Tải bản đầy đủ (.docx) (25 trang)

Trí tuệ nhân tạo Xây dựng chương trình cờ caro đánh với máy tính

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (263.82 KB, 25 trang )

TRƯỜNG ĐẠI HỌC BÁCH KHOA HÀ NỘI
KHOA CÔNG NGHỆ THÔNG TIN VÀ TRUYỀN THÔNG
----------------*---------------

BÁO CÁO MÔN: PROJECT 1
Đề tài:
Lập trình chương trình game cờ caro đánh với máy tính

Sinh viên thực hiện: Nguyễn Văn Thành
MSSV: 20102662
Lớp: CNTT 1 K55
Giảng viên hướng dẫn: TS. Trần Hải Anh

Hà Nội, Ngày 31/12/2014


MỤC LỤC
LỜI GIỚI THIỆU ………………………………………………………………. 3
PHẦN I. MÔ TẢ BÀI TOÁN………………………………………………….... 4
PHẦN II. GIẢI QUYẾT BÀI TOÁN………………………………………….... 5
A.
B.

C.

Ngôn ngữ thực hiện………………………………………………………. 5
Giải quyết bài toán………………………………………………………... 5
1. Thiết kế giao diện……………………………………………………… 5
2. Xây dựng chức năng và thiết kế lớp…………………………………… 6
a. Các chức năng cần thiết……………………………………………. 6
b. Phân tích và thiết kế lớp……………………………………………. 6


3. Xây dựng chức năng người chơi với người chơi……………………… 10
4. Thực hiện chức năng Undo, Redo……………………………………... 11
5. Xử lý thắng thua……………………………………………………….. 12
6. Trí tuệ nhân tạo cho máy trong cờ Caro………………………………..12
a. Sơ lược về trí tuệ nhân tạo…………………………………………..13
b. Trí tuệ nhân tạo trong cờ Caro………………………………………13
Giao diện và cách chơi……………………………………………………..14
1. Giao diện………………………………………………………………..15
2. Cách chơi……………………………………………………………….15

PHẦN III. MÃ NGUỒN………………………………………………………… 15
TÀI LIỆU THAM KHẢO……………………………………………………….. 24


LỜI GIỚI THIỆU
Cờ caro là trò chơi logic cổ xưa nhất trên Trái Đất. Nó được sáng tạo từ nhiều nền
văn minh khác nhau một cách độc lập. Nó bắt đầu xuất hiện từ năm 2000 trước CN
ở sông Hoàng Hà, Trung Quốc. Một số nhà khoa học đã tìm thấy bằng chứng
chứng minh Caro đã được phát minh ở Hy lạp cổ đại và ở Châu Mỹ trước thời
Colombo.Môn cờ cổ của Trung Quốc là Wutzu. Cờ Caro du nhập từ Trung Quốc
vào Nhật Bản từ khoảng năm 270 trước CN. Nó thường được gọi là Gomoku
nhưng cũng có các tên gọi khác tuỳ theo thời gian và địa phương như Kakugo,
gomoku-narabe, Itsutsu-ishi... Người ta đã tìm thấy một trò chơi cổ từ một di tích ở
Nhật năm 100 sau CN và thấy nó là một biến thể của Caro.Nó đã lan truyền nhanh
chóng với cái tên Kakugo (trò 5 quân). Các nhà sử học nói rằng vào các thế kỷ 17
và 18, mọi người đều chơi trò này-người già cũng như người trẻ.Năm 1858, khi
quyển sách đầu tiên về trò chơi này được xuất bản, nó được gọi là Kakugo. Nó tiếp
tục được chơi, được gọi với nhiều tên khác nhau như Goren, Goseki, rồi
Gomokunarabe, Gomoku và phát triển cho đến ngày nay thành thể loại phức tạp
nhất trong họ hàng đông đúc của nó, là Renju (chuỗi ngọc trai).

Trong cuộc sống hằng ngày, game cờ caro cũng rất phổ biến và được đa số các bạn
học sinh, sinh viên thường xuyên chơi. Nó vừa là một game giải trí rất tốt sau
những giờ học căng thẳng, vừa là trò chơi có tính trí tuệ cao kích thích tư duy của
người chơi.

3


PHẦN I. MÔ TẢ BÀI TOÁN
Xây dựng trò chơi cờ caro trên một bàn cờ gồm 20 hàng và 20 cột, tạo thành một
hình chữ nhật kích thước 20*20.
Các tính năng của trò chơi:
-

Tính năng cho 2 người chơi với nhau
Tính năng cho người chơi đấu với máy
Các chức năng khác như: New Game để bắt đầu ván chơi mới, Undo, Redo
cho phép đánh lại nước đi trước đó, Exit để đóng trò chơi.
Ngoài ra bổ sung thêm các chức năng phụ như Option lưu thông tin về luật
chơi, Help trợ giúp hướng dẫn cách chơi…

Trên cơ sở của đề tài em đã xây dựng chương trình game cờ caro đơn giản bao
gồm các tính năng trên.
Dù rất cố gắng nhưng chương trình của em chắc chắn không thể tránh khỏi những
thiếu xót, em mong nhận được những ý kiến nhận xét, góp ý của các thầy cô bộ
môn để chương trình được hoàn thiện hơn.

4



PHẦN II. GIẢI QUYẾT BÀI TOÁN
A. Ngôn ngữ thực hiện
Ngôn ngữ thực hiện chương trình: C#
Trình biên dịch sử dụng: Visual Studio 6.1
B. Giải quyết bài toán
1. Thiết kế giao diện
Tạo project mới: Mở visual studio, vào mục visual C#, chọn Windows forms
Application, đặt tên project: TroChoiCoCaro.
Cửa sổ chương trình ban đầu hiện lên giao diện Form1.cs[Design] dùng để thiết kế
giao diện. Mở hộp toolbox, trong mục Menus & Toolbars ta chọn Menu Strip, kéo
thả sang giao diện form. Ta đặt tên MenuStrip tương ứng với các chức năng như:
File (New Game, Exit), Edit (Undo, Redo), Option, Help. Trong New Game ta đặt
2 chế độ chơi là Player vs Player (người chơi với người) và Player vs Com (người
chơi với máy).
Các chức năng được sử dụng trong Toolbox:
-

PictureBox dùng để chèn ảnh biểu tượng trò chơi hiện thị trên giao diện.
Các Button dùng để xây dựng các nút: Player vs Player, Player vs Com,
New Game, Exit.
Panel là khung thực hiện trò chơi, để vẽ bàn cờ thực hiện các chức năng chơi
cờ.

Tùy vào thẩm mỹ của người thiết kế, ta có thể chỉnh sửa màu sắc, size, cỡ chữ
cho các chức năng trên trong mục System.Windows.Forms.

5


2. Xây dựng chức năng và thiết kế lớp

a. Các chức năng cần thiết
- Chơi game: Chơi với người, chơi với máy.
- Xử lý thắng thua.
- Các chức năng Edit( Undo, Redo): cho phép đánh lại và khôi phục lại nước đi.
- Trí tuệ cho máy tính: Có thể chia theo level: Dễ, Trung Bình, Khó.
- Thêm 1 vài chức năng khác như: Save Game, Load Game…
Ban_Co
int SoDong;
int SoCot;
Void VeBanCo(graphics g,
O_Co[][] MangOCo)
Void VeQuanCo(graphics
g, int Dong, int Cot, image
img O_Co[][] MangOCo)
// AI: Trí tuệ nhân tạo
b. Phân tích thiết kế lớp
O_Co
const int ChieuDai;
const int ChieuRong;
const int SoHuu;

CaroChess

O_Co[][]Mang_O_Co;
Ban_Co ban_co;
Void VeOco(graphics g, int
Int LuotDi;
SoHuu)
Bool SanSang;
Stack<O_Co> Cac_Nuoc_Da_Di;

Stack <O_Co> Cac_Nuoc_Di_Lai;
Void KhoiTaoMangOCo()
Void VeBanCo(graphics g)
Void XacDinhOCo(int x, int y,
• Lớp Ô cờ:
Graphics g)
Thực hiện các chức năng lưu thông tin về ô cờ nhưVoid
chiềuChoiVoiNguoi()
dài, chiều rộng, vị
Void
ChoiVoiMay()
trí dòng, vị trí cột, tình trạng ô cờ: còn trống hay đã được đánh(sở hữu), và
Bool DuyetChienThang()
được sở hữu bởi quân cờ nào…
// AI: Trí tuệ nhân tạo
6


class OCo
{
public const int _ChieuRong = 25;
public const int _ChieuCao = 25;
private int _Dong;
public int Dong
{
get { return _Dong; }
set { _Dong = value; }
}
private int _Cot;
public int Cot

{
get { return _Cot; }
set { _Cot = value; }
}
private Point _ViTri;
public Point ViTri
{
get { return _ViTri; }
set { _ViTri = value; }
}
private int _SoHuu;
public int SoHuu
{
get { return _SoHuu; }
set { _SoHuu = value; }
}
public OCo()
{
}
public OCo(int dong, int cot, Point vitri, int sohuu)
{
_Dong = dong;
_Cot = cot;
_ViTri = vitri;
_SoHuu = sohuu;
}


Lớp Bàn cờ:


7


Lớp này thực hiện các chức năng lưu thông tin về bàn cờ như số dòng, số
cột. Đồng thời thực hiện các chức năng như: vẽ bàn cờ, vẽ quân cờ, xóa
quân cờ…
class BanCo
{
private int _SoDong;
public int SoDong
{
get { return _SoDong; }
}
private int _SoCot;
public int SoCot
{
get { return _SoCot; }
}
public BanCo()
{
_SoDong = 0;
_SoCot = 0;
}
public BanCo(int soDong, int soCot)
{
_SoCot = soCot;
_SoDong = soDong;
}
public void VeBanCo(Graphics g)
{

for (int i = 0; i <= _SoCot; i++)
{
g.DrawLine(CaroChess.pen, i * OCo._ChieuRong, 0, i *
OCo._ChieuRong, _SoDong * OCo._ChieuCao);
}
for (int j = 0; j <= _SoDong; j++)
{
g.DrawLine(CaroChess.pen, 0, j * OCo._ChieuCao, _SoCot *
OCo._ChieuRong, j * OCo._ChieuCao);
}
}
public void VeQuanCo(Graphics g, Point point, SolidBrush sb)
{
g.FillEllipse(sb, point.X + 2, point.Y + 2, OCo._ChieuRong - 4,
OCo._ChieuCao - 4);
}
public void XoaQuanCo(Graphics g, Point point, SolidBrush sb)
{
8


g.FillRectangle(sb, point.X +1, point.Y +1, OCo._ChieuRong-2,
OCo._ChieuCao-2);
}
}



Lớp CaroChess
Đây là lớp quan trọng nhất cũng là lớp thực hiện hầu hết các chức năng của

trò chơi, lưu trữ thông tin về cờ như trạng thái của trò chơi, các bước đi, chế
độ chơi, lượt đi của mỗi bên, kiểm tra chiến thắng, đánh với người hay đánh
với máy.... Trong chức năng đánh với máy còn có các hàm khởi tạo
computer, tìm kiếm nước đi…

class CaroChess
{
public static Pen pen;
public static SolidBrush sbRed;
public static SolidBrush sbBlack;
public static SolidBrush sbGreenWhite;
private OCo[,] _MangOCo;
private BanCo _BanCo;
private int _CheDoChoi;
public int CheDoChoi
{
get { return _CheDoChoi; }
}
private int _LuotDi;
private bool _SanSang;
public bool SanSang
{
get { return _SanSang; }
}
private Stack<OCo> stkCacNuocDaDi;
private Stack<OCo> stkCacNuocUndo;
private KETTHUC _KetThuc;
public CaroChess()
{
pen = new Pen(Color.Blue);

sbRed = new SolidBrush(Color.Red);
sbBlack = new SolidBrush(Color.Black);
sbGreenWhite = new SolidBrush(Color.FromArgb(192, 255, 255));
9


_BanCo = new BanCo(20, 20);
_MangOCo = new OCo[_BanCo.SoDong, _BanCo.SoCot];
stkCacNuocDaDi = new Stack<OCo>();
stkCacNuocUndo = new Stack<OCo>();
_LuotDi = 1;
}

3. Xây dựng chức năng người chơi với người chơi
Chức năng này sẽ thực hiện việc đánh cờ giữa 2 người chơi dùng chuột: Lần lượt
người chơi thứ nhất đánh sau đó đến lượt người chơi thứ 2 đánh. Các class được sử
dụng để thực hiện chức năng này: CaroChess, Ban_Co, O_Co.
Trong hàm khởi tạo người đánh với người (Player vs Player) ta lưu trữ các thông
tin sau:
-

_SanSang = true; Thể hiện trạng thái của bàn cờ, đã sẵn sàng để được chơi
hay chưa (có thể bắt đầu đánh cờ được hay chưa).
_LuotDi = 1 ; Gán ngay bên đi đầu tiên là lượt đi bằng 1 và làm người chơi
thứ nhất.
_CheDoChoi = 1: Người đánh với người.
KhoiTaoMangOco(); Gọi đến hàm KhoiTaoMangOco và thực hiện chức
năng lưu thông tin của mảng ô cờ mới được gọi.
VeBanCo(g); Thực hiện chức năng vẽ bàn cờ với biến graphics g.


public void StartPlayerVsPlayer(Graphics g)
{
_SanSang = true;
stkCacNuocDaDi = new Stack<OCo>();
stkCacNuocUndo = new Stack<OCo>();
_LuotDi = 1;
_CheDoChoi = 1;
KhoiTaoMangOCo();
VeBanCo(g);
}

4. Thực hiện chức năng Undo, Redo
Để thực hiện 2 chức năng này ta lưu danh sách các nước đã đi và danh sách
các nước đã được undo vào các Stack <ô cờ> (chức năng Redo chỉ thực hiện
được sau khi sử dụng chức năng Undo).

10


public void Undo(Graphics g)
{
if (stkCacNuocDaDi.Count != 0)
{
OCo oco = stkCacNuocDaDi.Pop();
stkCacNuocUndo.Push(new OCo(oco.Dong, oco.Cot, oco.ViTri,
oco.SoHuu));
_MangOCo[oco.Dong, oco.Cot].SoHuu = 0;
_BanCo.XoaQuanCo(g, oco.ViTri, sbGreenWhite);
if (_LuotDi == 1)
_LuotDi = 2;

else
_LuotDi = 1;
}
}
public void Redo(Graphics g)
{
if (stkCacNuocUndo.Count != 0)
{
OCo oco = stkCacNuocUndo.Pop();
stkCacNuocDaDi.Push(new OCo(oco.Dong, oco.Cot, oco.ViTri,
oco.SoHuu));
_MangOCo[oco.Dong, oco.Cot].SoHuu = oco.SoHuu;
_BanCo.VeQuanCo(g, oco.ViTri, oco.SoHuu == 1 ? sbBlack :
sbRed);
if (_LuotDi == 1)
_LuotDi = 2;
else
_LuotDi = 1;
}
}

11


5. Xử lý thắng thua
Như theo luật chơi, nếu bên nào xếp được 5 quân của mình theo một phương bất
kỳ (ngang, dọc, chéo) bất kỳ trước mà không bị quân đối phương chặn 2 đầu thì
bên đó được xem như là thắng cuộc. Khi đó sẽ có một thông báo hiện ra cho người
chơi biết kết quả đồng thời dừng ván chơi ngay tại thời điểm đó.
6. Trí tuệ nhân tạo cho máy trong cờ caro

a. Sơ lược về trí tuệ nhân tạo
Trí tuệ nhân tạo hay trí thông minh nhân tạo (Tiếng Anh: Artification intelligence
thường được viết tắt là AI) là trí tuệ được biểu diễn bởi bất cứ hệ thống nhân tạo
nào. Thuật ngữ này dùng đển nói đến các máy tính có được trí tuệ của một phần
não người, thông qua việc nghiên cứu của các ngành khoa học khiến máy tính có
được suy nghĩ, suy luận như con người.
Trí tuệ nhân tạo được chia làm 2 trường phái: Truyền thống và trí tuệ tính toán.
b. Trí tuệ nhân tạo trong cờ Caro
Sử dụng cấu trúc cây:

Tại lượt đi A, tìm kiếm các nước đi có thể đánh B và C.
Tính trước các nước D, E tương ứng với B và F, G,H tương ứng với C.
Tiếp tục tính các nước đi tương ứng với E và M, N tương ứng với I.
Tương ứng với mỗi nước đi, cần lần lượt quét các hướng:

12


Lần lượt duyệt các hướng và tính toán cả việc tấn công và phòng ngự có lợi ở nước
đó.
Dùng 2 mảng để lưu trữ điểm tấn công và phòng ngự:
-

Điểm tấn công = {0, 9, 54, 162, …}
Điểm phòng ngự = {0, 3, 27, 99, …}

Theo hướng đó có bao nhiêu quân thì ta cộng vào bấy nhiêu điểm tấn công hay
phòng ngự và ngược lại.
Lặp lại với các nước con có thể đánh ở lượt sau.
C. Giao diện và cách chơi

1. Giao diện

13


2. Cách chơi
Giao diện chơi game rất đơn giản và trực quan. Các chức năng chính là New
Game, Exit, Player vs Player và Player vs Com. Trò chơi hỗ trợ được 2 người
chơi với nhau và người chơi đánh với máy. Ngoài ra còn có các chức năng như
Undo, Redo để chơi lại và khôi phục lại nước đi.
Để chơi với 2 người người chơi có thể ấn vào nút “Player vs Player” hoặc vào
File  New  Player vs Player. Để giành được chiến thắng thì người chơi phải
tạo được 1 dãy 5 con liên tiếp cùng màu theo 1 phương bất kỳ (dọc, ngang,
chéo) và thỏa mãn điều kiện không bị đối phương chặn 2 đầu.
Để chơi với máy người chơi có thể ấn vào nút “Player vs Com” hoặc cũng vào
File  New  Player vs Com.
14


Sau mỗi ván chơi, người chơi click vào nút New Game để chơi ván mới hoặc
thoát ra khỏi trò chơi bằng nút Exit.

15


PHẦN III. MÃ NGUỒN
1.

Các hàm thư viện sử dụng
using

using
using
using
using
using
using

2.

using
using
using
using
using
using

System;
System.Collections.Generic;
System.Drawing;
System.Linq;
System.Text;
System.Threading.Tasks;
System.Windows.Forms;

Lớp Ô cờ
System;
System.Collections.Generic;
System.Drawing;
System.Linq;
System.Text;

System.Threading.Tasks;

namespace TroChoiCoCaro
{
class OCo
{
public const int _ChieuRong = 25;
public const int _ChieuCao = 25;
private int _Dong;
public int Dong
{
get { return _Dong; }
set { _Dong = value; }
}
private int _Cot;
public int Cot
16


{
get { return _Cot; }
set { _Cot = value; }
}
private Point _ViTri;
public Point ViTri
{
get { return _ViTri; }
set { _ViTri = value; }
}
private int _SoHuu;

public int SoHuu
{
get { return _SoHuu; }
set { _SoHuu = value; }
}
public OCo()
{
}
public OCo(int dong, int cot, Point vitri, int sohuu)
{
_Dong = dong;
_Cot = cot;
_ViTri = vitri;
_SoHuu = sohuu;
}
}
}
3.

using
using
using
using
using

Lớp Bàn cờ
System;
System.Collections.Generic;
System.Drawing;
System.Linq;

System.Text;
17


namespace TroChoiCoCaro
{
class BanCo
{
private int _SoDong;
public int SoDong
{
get { return _SoDong; }
}
private int _SoCot;
public int SoCot
{
get { return _SoCot; }
}
public BanCo()
{
_SoDong = 0;
_SoCot = 0;
}
public BanCo(int soDong, int soCot)
{
_SoCot = soCot;
_SoDong = soDong;
}
public void VeBanCo(Graphics g)
{

for (int i = 0; i <= _SoCot; i++)
{
g.DrawLine(CaroChess.pen, i * OCo._ChieuRong, 0,
i * OCo._ChieuRong, _SoDong * OCo._ChieuCao);
}
for (int j = 0; j <= _SoDong; j++)
{
g.DrawLine(CaroChess.pen, 0, j * OCo._ChieuCao,
_SoCot * OCo._ChieuRong, j * OCo._ChieuCao);
}
}
18


public void VeQuanCo(Graphics g, Point point, SolidBrush
sb)
{
g.FillEllipse(sb, point.X + 2, point.Y + 2,
OCo._ChieuRong - 4, OCo._ChieuCao - 4);
}
public void XoaQuanCo(Graphics g, Point point, SolidBrush
sb)
{
g.FillRectangle(sb, point.X +1, point.Y +1,
OCo._ChieuRong-2, OCo._ChieuCao-2);
}
}
}
4.
using

using
using
using
using
using
using

Lớp CaroChess
System;
System.Collections.Generic;
System.Drawing;
System.Linq;
System.Text;
System.Threading.Tasks;
System.Windows.Forms;

namespace TroChoiCoCaro
{
//
//
//
//

Các phương thức khởi tạo
Undo và Redo
Check Win
AI

}


5.

using
using
using
using

Lớp Form1.cs

System;
System.Collections.Generic;
System.ComponentModel;
System.Data;
19


using
using
using
using
using

System.Drawing;
System.Linq;
System.Text;
System.Threading.Tasks;
System.Windows.Forms;

namespace TroChoiCoCaro
{

public partial class frmCoCaro : Form
{
private CaroChess caroChess;
private Graphics grs;
public frmCoCaro()
{
InitializeComponent();
btnPlayerVsPlayer.Click += new EventHandler(PvsP);
caroChess = new CaroChess();
caroChess.KhoiTaoMangOCo();
grs = pnlBanCo.CreateGraphics();
playerVsComToolStripMenuItem.Click += new
EventHandler(PvsC_Click);
btnPlayerVsCom.Click += new EventHandler(PvsC_Click);
}
//private void button4_Click(object sender, EventArgs e)
//{
//
Application.Exit();
//}
private void menuStrip1_ItemClicked(object sender,
ToolStripItemClickedEventArgs e)
{
}
private void frmCoCaro_Load(object sender, EventArgs e)
{
}

20



private void pnlBanCo_Paint(object sender, PaintEventArgs
e)
{
caroChess.VeBanCo(grs);
caroChess.VeLaiQuanCo(grs);
}
private void frmCoCaro_Paint(object sender,
PaintEventArgs e)
{
}
private void pnlBanCo_MouseClick(object sender,
MouseEventArgs e)
{
if (!caroChess.SanSang)
return;
if (caroChess.CheDoChoi == 1)
{
caroChess.DanhCo(e.X, e.Y, grs);
if (caroChess.KiemTraChienThang())
{
caroChess.KetThucTroChoi();
return;
}
}
if (caroChess.CheDoChoi == 2)
{
caroChess.DanhCo(e.X, e.Y, grs);
caroChess.KhoiDongComputer(grs);
if (caroChess.KiemTraChienThang())

{
caroChess.KetThucTroChoi();
return;
}
}
}
private void PvsP(object sender, EventArgs e)
21


{
grs.Clear(pnlBanCo.BackColor);
caroChess.StartPlayerVsPlayer(grs);
}
private void PvsC_Click(object sender, EventArgs e)
{
grs.Clear(pnlBanCo.BackColor);
caroChess.StartPlayerVsCom(grs);
}
private void undoToolStripMenuItem_Click(object sender,
EventArgs e)
{
//grs.Clear(pnlBanCo.BackColor);
caroChess.Undo(grs);
}
private void redoToolStripMenuItem_Click(object sender,
EventArgs e)
{
caroChess.Redo(grs);
}

private void btnNewGame_Click(object sender, EventArgs e)
{
grs.Clear(pnlBanCo.BackColor);
caroChess.VeBanCo(grs);
}
private void btnExit_Click(object sender, EventArgs e)
{
Application.Exit();
}
private void editToolStripMenuItem_Click(object sender,
EventArgs e)
{
Application.Exit();
}
}
}
22


6.

Form1.cs [Design]

7.

Form1.Designer.cs

namespace TroChoiCoCaro
{
// Code tự sinh trong quá trình tạo

}

8.

Program.cs

23


using
using
using
using
using
using

System;
System.Collections.Generic;
System.Drawing;
System.Linq;
System.Threading.Tasks;
System.Windows.Forms;

namespace TroChoiCoCaro
{
static class Program
{

}


/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new frmCoCaro());
}

}

24


TÀI LIỆU THAM KHẢO
-Hướng dẫn lập trình C# nâng cao – MinhDangThanh’blog
/>-Bài giảng hướng dẫn lập trình c# căn bản, lập trình c# nâng cao của anh Lê
Nguyễn Anh Huy

25


×