Đề tài: Huấn luyện AI chơi game Tetris... Cảm ơn thầy Huỳnh Tuấn Anh vì đã cung cấp cho nhóm chúng em nói riêng và các sinh viên trong lớp nói chung những đề tài đồ án thú vị ạ, nhóm chú
Trang 1ĐẠI HỌC QUỐC GIA THÀNH PHỐ HỒ CHÍ MINH TRƯỜNG ĐẠI HỌC CÔNG NGHỆ THÔNG TIN
KHOA CÔNG NGHỆ PHẦN MỀM
Trang 2������
Trang 4ĐỒ ÁN 1
Trang 5Đề tài: Huấn luyện AI chơi game Tetris
Trang 6Giáo viên hướng dẫn: Huỳnh Tuấn Anh
Trang 7Sinh viên thực hiện: Dư Chế Anh - 18520445
Trang 8Thi Thanh Chương -18520539
Trang 9Thành phố Hồ Chí Minh, tháng 6 năm 2021
Trang 10ĐỒ ÁN 1 TETRIS AI
Trang 11TETRIS AI
Trang 12TÀI LIỆU BÁO CÁO DỰ
ÁN Date: 28/06/2021
Trang 14Authors:
Trang 15- Dư Chế Anh
Trang 16- Thi Thanh Chương
Trang 17Contacts:
Trang 18- 18520445@gm.uit.edu.vn – 18520539@gm.uit.edu.vn
Trang 192
Trang 20ĐỒ ÁN 1 TETRIS AI
Trang 21NHẬN XÉT
Trang 22Trang 23
3
Trang 24ĐỒ ÁN 1 TETRIS AI
Trang 25LỜI CẢM ƠN
Trang 26Cảm ơn thầy Huỳnh Tuấn Anh vì đã cung cấp cho nhóm chúng em nói riêng
và các sinh viên trong lớp nói chung những đề tài đồ án thú vị ạ, nhóm chúng
em đã có những kiến thức mới và kinh nghiệm làm việc nhóm thú vị Trong quá trình làm việc, khó tránh được các sai sót từ nhỏ đến lớn, mong các thầy
cô quan tâm và góp ý tận tình giúp nhóm chúng em có thể phát triển hơn ạ
Trang 27Cảm ơn các thầy cô ạ!
Trang 284
Trang 29ĐỒ ÁN 1 TETRIS AI
Trang 30Contents
Trang 32KẾ HOẠCH VÀ PHÂN TÍCH 7
1 Kế hoạch 7
2 Phân tích 7
Trang 33Thực thi 10
1 Tetris: 10
2 Bot: 10
3 AI 11
Trang 34NGHIÊN CỨU 12Case #1: Ai cơ bản train ở 1 seed 13Case #2: AI cơ bản train ở nhiều seed 14Case #3: AI với fitness mới và train ở 1 seed 15Case #4: AI tự train vs AI tự train 17
Trang 35KẾT LUẬN 19THAM KHẢO 20Records 21
Trang 36Record1 .21Record 2 21Record 3 22Record 4 23Record 5 23
Trang 375
Trang 38ĐỒ ÁN 1 TETRIS AI
Trang 39GIỚI THIỆU
Trang 401 Mục đích tài liệu:
Trang 41Tài liệu này được xây dựng với mục đích giới thiệu đề tài, ghi nhận và phântích quá trình tiếp nhận và giải quyết vấn đề, từ ban đầu cho đến khi hoànthành đồ án
Trang 422 Đề tài:
Trang 43Huấn luyện AI chơi game Tetris
Trang 443 Vấn đề:
Trang 45Tetris mà một tựa game cần chú ý nhiều input
Trang 46Các sinh viên thực hiện đề tài không có kinh nghiệm với học máy hoặc trí tuệ nhân tạo
Trang 474 Hướng giải quyết:
Trang 48Sử dụng ngôn ngữ lập trình Python cho dự án, vì số lượng tài liệu và hướng dẫn lớn
Trang 49Sử dụng Reinforcement learning để huấn luyện cho AI (huấn luyện tăng cường)
Trang 505 Mục tiêu:
Trang 51Tạo ra AI có thể đạt được 1,000,000 điểm trong tetris
Trang 526
Trang 53ĐỒ ÁN 1 TETRIS AI
Trang 54KẾ HOẠCH VÀ PHÂN TÍCH
Trang 551 Kế hoạch
Trang 56Bước Mục đích
Nghiên cứu Nghiên cứu về Python và ứng dụng trong AI
Thực thi Tạo ra bot chơi Tetris, bot sẽ được feed thông số đầu vào
Huấn luyện Biến bot thành AI, bot sẽ tự huấn luyện để lấy thông số đầu vào
Trang 592 Phân tích
Trang 60- Tetris: Mục tiêu của trò chơi là di chuyển các khối gạch đang rơi từ từ xuống
trong kích thước hình chữ nhật 20 hàng x 10 cột (trên màn hình) Chỗ nào cógạch rồi thì không di chuyển được tới vị trí đó Người chơi xếp những khốihình sao cho khối hình lấp đầy 1 hàng ngang để ghi điểm và hàng ngang ấy
sẽ biến mất (Source: Wikipedia)
Trang 61- Cách một cá nhân thông thường chơi Tetris:
Trang 62• Đảm bảo độ cao hiện tại là tối thiểu: Khi các viên gạch Tetris chạm
đến độ cao tối đa, thì người chơi sẽ thua cuộc, nên ta phải đảm bàođược khi chọn vị trí đặt gạch ta sẽ tối ưu độ cao của chúng
Trang 64• Đảm bảo người chơi clear được số hàng nhiều nhất (ghi điểm nhiều nhất): Người chơi sẽ ghi điểm trong game bằng các clear các
hàng, nên điểm sẽ là cách chúng ta độ hiệu quả của AI
Trang 657
Trang 66ĐỒ ÁN 1
Trang 67TETRIS AI
Trang 69• Đảm bảo số lượng lỗ giữa cột là nhỏ nhất: Các lỗ giữa hàng sẽ gây
cản trở cho việc ghi điểm và clear nhiều hàng một lúc nên chúng ta phảiđảm bảo cho việc trên
Trang 71• Đảm bảo các cột có độ lệch không lớn: Nếu độ lệch giữa các cột gần
nhau lớn thì chúng sẽ giới hạn số lượng gạch có thể được đặt trênchúng nên cần phải tối ưu thông số này
Trang 73Bumpiness = |3-5| + |5-6| + |6-5| + |5-4| + |4-5| = 6
Trang 748
Trang 75ĐỒ ÁN 1 TETRIS AI
Trang 76- AI phù hợp: Sau nhiều lần nghiên cứu tài liệu và video hướng dẫn thì nhóm
đã đi đến quyết định chọn Genetic Algorithm (thuật toán di truyền) để sử dụngcho hệ thống
Trang 77• Giới thiệu về thuật toán di truyền: Là một mô hình mô phỏng thuyết
chọn lọc tự nhiên của
Darwin
Trang 78• Quy trình thực thi thuật toán di truyền:
Trang 791 Tạo ra một quần thể với số lượng các cá
Trang 80thể cho trước mang thông số ngẫu nhiên
Trang 812 Tính fitness cho từng cá thể trong quần
Trang 82thể (fitness là hàm tính độ phù hợp cho hệ
Trang 83thống, như tetris thì fitness sẽ trả về số
Trang 84điểm hoặc số line đã clear)
Trang 853 Chọn ra các các thể có fitness cao nhất để
Trang 86đi lai tạo
Trang 874 Các cá thể lai tạo sẽ tạo ra các cá thể mới,
Trang 88có thông số ngẫu nhiên (có thể lớn hoặc
Trang 89nhỏ hơn cá thể bố và mẹ)
Trang 905 Các cá thể vừa được lai tạo có tỉ lệ nhỏ bị
Trang 91đột biến, khi cá thể bị đột biến, thông số
Trang 92của cá thể sẽ có sự thay đổi
Trang 936 Sau đó chúng ta tổng hợp các cá thể tốt
Trang 94nhất của đời này và các cá thể mới được
Trang 95lai tạo, tổng hợp lại để tạo thành một quần
Trang 96thể mới
Trang 977 Lặp lại quá trình cho đến khi nào đạt nhu
Trang 98cầu của người lập trình, ta thu kết quả và
Trang 99lấy cá thể có kết quả tốt nhất.
Trang 1009
Trang 101ĐỒ ÁN 1 TETRIS AI
Trang 102Thực thi
Trang 1031 Tetris:
Trang 104Vì đây là đồ án huấn luyện AI chơi game tetris nên nhóm
Trang 105bỏ qua bước phát triển game tetris bằng cách clone một
Trang 106tetris repo trên github về và phát triển tính năng AI thêm
Trang 107vào repo trên (info)
Trang 108Repo có chức năng cơ bản, đọc dễ hiểu và có khả năng
Trang 109chỉnh sửa cao
Trang 1102 Bot:
Trang 111Trong quá trình phân tích nhóm đã đưa ra các thông số cần đảm bảo với mộtngười chơi tetris thông thường, đó là: Độ cao hiện tại, số dòng clear, số lượng
lỗ giữa các cột, độ lệch giữa các cột Để chơi tetris người chơi phải cân nhắccác thông số trên (ở tốc độ của con người) để đưa ra quyết định hợp lý, máytính cũng tương tự, ta sẽ đưa các thông số vào cho AI để chúng quyết địnhxem nên tiến hành bước gì tiếp theo
Trang 112Nhưng chỉ các thông số trên thôi thì là chưa đủ, chúng đều quan trọng, nhưngđối với người chơi thì clear dòng sẽ quan trọng hơn thảy và người chơi sẽquan trọng việc hạn chế lỗ giữa cột hơn là đảm bảo độ cao hiện tại Vì vậy đểđưa ra lựa chọn chính xác, ta phải cho mỗi thông số một cân nặng (weight)
Trang 113Từ đó ta đưa ra cách tínhđiểm:
Trang 114a=Aggregate Height
Trang 115b=Lines Clear
Trang 116c=Holes
Trang 117d=Bumpiness
Trang 118score = a*w1 + b*w2 + c*w3 + d*w4
Trang 119Với w1 – w4 là các weight tương ứng
Trang 120với các thông số trên Và khi xử lý, ta
Trang 121sẽ đọc tất cả vị trí phù hợp với viên
Trang 122gạch hiện tại, tính score cho từng vị trí,
Trang 123và chọn vị trí có score là tốt nhất để đặt
Trang 124viên gạch của chúng ta.
Trang 12510
Trang 126ĐỒ ÁN 1
Trang 127TETRIS AI
Trang 128Sau khi tạo nên các hàm tính toán cần thiết, ta chỉ việc cho chúng 1 bộ
weights phù hợp May mắn thay có một dự án có bộ weights sẵn cho các thông số tương tự nên ta có thể áp dụng chúng vào bot để cho bot hoạt động được
Trang 129Weights= [-0.510066, 0.760666, -0.35663, -0.184483] (source)
Trang 1303 AI
Trang 131Đầu tiên nhóm tạo một class chứa các hàm của thuật toán tiến hóa
Trang 132Trong đó hàm tính fitness của một quần thể sẽ trả về điểm số của từng cá thể trong một quần thể với một số lượng gạch giới hạn nào đó (piecelimit)
Trang 133Sau đó chúng ta áp dụng mô hình thuật toán tiến hóa để dự án có thể tự đưa
ra weights phù hợp
Trang 13411
Trang 135ĐỒ ÁN 1 TETRIS AI
Trang 136NGHIÊN CỨU
Trang 137Lưu ý:
Trang 138• Các kết quả nghiên cứu sau được tiến hành với fastmode=True (các viên
gạch được rơi ngay) và sẽ không hiển thị GUI để đảm bảo độ chính xác của kết quả (hiển thị GUI sẽ ảnh hưởng đến kết quả tùy theo sức mạnh của phần cứng)
Trang 139• Weight được lấy từ ví dụ trên mạng trong bài đã được huấn luyện rất
nhiều và có thể chơi tetris liên tục 2 tuần liền, và clear hơn 2 triệu lines
mà vẫn không có dấu hiệu thua cuộc, nên nhóm thấy đây sẽ là một mốc so sánh tốt
Trang 140• Kiểm tra file readme.md trên github repo nếu muốn tiến hành thực thi lại các thí nghiệm sau
Trang 141Để đánh giá kết quả nghiên cứu, chúng ta sẽ so sánh kết quả khi dùng weight huấn luyện được với dùng weight của ví dụ trên mạng
Trang 142Weight trên mạng ta sẽ gọi là demo_weight =
Trang 143[-0.510066, 0.760666, -0.35663, -0.184483]
Trang 144Weight ta train được sẽ gọi là trained_weight
Trang 14512
Trang 146ĐỒ ÁN 1 TETRIS AI
Trang 147Case #1: Ai cơ bản train ở 1 seed
Trang 148Đặc điểm:
Trang 149▪ Các viên gạch và thứ tự gạch xuất hiện luôn là giống nhau (dùng seed)
▪ AI được train với seed=1
Trang 150Fitness được đo bằng số điểm đạt được
Trang 151Kết quả huấn luyện ( record1 )
Trang 152Max fitness
Weight của cá thể tốt nhất
5 5 2 100 7072 [-1.23962156, -1.19593245,
-1.23643526, - 1.21559287]
Trang 155Bảng so sánh
Trang 156STT seed Số
lượnggạch
Trained fitness Demo fitness fitness/demoTrain
(lost)
>2.000.000(stopped)
Trang 158Phân tích case#1:
Trang 159- AI của chúng ta được huấn luyện qua 5 đời,mỗi đời gồm 5 cá thể, với giới hạn gạch là 100
Trang 160- Ở 1.1, các thông số của được set tương tựnhư lúc AI được huấn luyện, nên điểm số cho
ra đạt được 93% so với demo_weight
Trang 161- Ở 1.2, vì số lượng gạch tăng lên nhiều hơn
so với lúc huấn luyện nên tỉ lệ khi mang ra sosánh là 87% so với demo_weight
Trang 162- Ở 1.3, 1.4, 1.5, trained_weight cho ra sốđiểm thấp hơn rõ rệt khi so với demo_weight(ở 1.3 ta đạt 18%)
Trang 163Kết luận case#1:
Trang 164- Cần có thêm nhiều cá thể ở mỗi đời, thêm nhiều đời cho quần thể
Trang 165- Huấn luyện với seed nhất định sẽ tối ưu thời
gian (nếu ta kiểm thử ở seed đó), nhưng khả năng cao sẽ phát sinh vấn đề khi sử dụng AItrong thực tiễn
Trang 16613
Trang 167ĐỒ ÁN 1 TETRIS AI
Trang 168Case #2: AI cơ bản train ở nhiều seed
Trang 169Đặc điểm:
Trang 170▪ Các viên gạch và thứ tự gạch xuất hiện luôn là ngẫu nhiên
▪ AI được train với seed=random
Trang 171Fitness được đo bằng số điểm đạt được
Trang 172Kết quả huấn luyện ( record2 )
Trang 173Max fitness
Weight của cá thể tốt nhất
10 10 2 500 169,382 [-0.95302473, 0.18715282,
-2.16764887, - 0.34569755]
Trang 176Bảng so sánh
Trang 177STT seed Số
lượnggạch
Trained fitness Demo fitness fitness/demoTrain
fitness2.1 1 500 29,795 146,396 0.2035232.2 2 500 1,612 146,398 0.0110112.3 3 500 159,545 145,229 1.0985752.4 4 500 18,009 151,914 0.1185472.5 5 500 37,861 144,788 0.261493
2.6 3 infinite 247.437
(lost)
>3.000
00 0 (stopped)
Trang 179Phân tích case #2:
Trang 180- AI của chúng ta được huấn luyện qua 10 đời,mỗi đời gồm 10 cá thể,với giới hạn gạch là
500
Trang 181- Khi thực chiến mỗi cá thể có giới hạn gạch
và seed khác nhau
Trang 182- Ngoại trừ ở 2.3, thì trained_weight luôn đạt tỉ
lệ rất thấp khi so với demo_weight (2.2 đạt1,1%)
Trang 183- Ở 2.3 ta đạt 110%
Trang 184- Ta thua ở 247,437 điểm ở hàng 2.6 (khônggiới hạn gạch), vẫn không đạt yêu cầu
Trang 185Kết luận case#2:
Trang 186- Huấn luyện AI với các seed ngẫu nhiên có khả năng tạo ra AI tốt hơn
Trang 187- Huấn luyện AI với seed ngẫu nhiên cần rất
nhiều cá thể mỗi đời và cần qua rất nhiều đời để đạt được khả năng ứng dụng cao (khi sovới ứng dụng trong case #2)
Trang 18814
Trang 189ĐỒ ÁN 1
Trang 190TETRIS AI
Trang 191Case #3: AI với fitness mới và train ở 1 seed
Trang 192Đặc điểm:
Trang 193▪ Các viên gạch và thứ tự gạch xuất hiện được xác định bằng seed
▪ AI được train với seed=1
Trang 194Fitness được đo bằng:
Trang 195số lượng line đã xóa*1000 + số lượng gạch đã đặt
Trang 196Kết quả huấn luyện ( record3 )
Trang 197Max fitness
Weight của cá thể tốt nhất
5 5 2 300 118300 [-0.31534278, 0.2462788,
-1.93889201, - 0.48623653]
Trang 200Bảng so sánh
Trang 201Lưu ý:
Trang 202▪ Ở bảng này ta thêm một thông số nằm sau fitness của 2 weight, đó hiệu
năng của fitness, mỗi viên gạch tetris có 4 khối nhỏ, cần 10 khối nhỏ để tạo thành một hàng ngang, cách tính fitness mới là: số lượng line đã xóa*1000 + số lượng gạch đã đặt, vì vậy theo lý thuyết, nếu đặt 300 viên gạch:
Trang 203->fitness tối đa có thể đạt được là: 300*0.4*1000 + 300 =120,300
Trang 204▪ Ta sẽ giới thiệu thêm thông số nằm sau fitness của 2 weight đó là tỉ lệ
của chúng so với fitness tối đa đối với thông số của chúng
Trang 205▪ Với các bài train ở số lượng gạch là vô hạn thì chúng sẽ được giám sát
kèm với UI nên số điểm có thể thấp hơn số điểm tối đa.
Trang 206STT seed Số
lượnggạch
Theoreticalmaximum
Trained fitness Demo fitness Train fitne
ss/ dem
o fitness3.1 1 300 120,300 118,300
(98.3%)
117,300 (97.5%)
1.008
3.2 1 600 240,600 236,600
(98.3%)
237,600 (98.7%)
0.995
3.3 1 1,000 401,000 399,000
(99.5%)
400,000 (99.7%)
0.997
Trang 20915
Trang 210ĐỒ ÁN 1
Trang 211TETRIS AI
Trang 2123.4 2 500 200,500 198,500
(99%)
196,500 (98%)
1.010
3.5 2 Infinite Infinite 1,535,862
(99%,thua
ở line3862)
1,049,655 (98.5%, thua
ở line 2655)
1.463
Trang 215Phân tích case #3:
Trang 216▪ AI của chúng ta được huấn luyện qua 5 đời, mỗi đời gồm 5 cá thể, với giới hạn gạch là 300
Trang 217▪ Khi thực chiến với demo_weight mỗi cá thể có giới hạn gạch và seed khác nhau
Trang 218▪ Kết quả khi mang ra so sánh với demo_fitness là thật sự xuất sắc, chỉqua tổng cộng 25 cá thể mà AI đã có thể ngang bằng so vớidemo_fitness ở 4/5 bài test và chạm ngưỡng 146% hiệu năng khi dùngseed 2 và không giới hạn gạch
Trang 219▪ Có sự khác biệt rất lớn khi thay đổi công thức tính fitness
Kết luận case#3:
Trang 220▪ Huấn luyện AI tetris muốn đạt hiệu quả cao, nhanh chóng thì ta tínhfitness bằng số lượng line đã clear, sẽ tốt hơn là tính điểm, vì công thứctính điểm là số lượng line clear = [1,2,3,4] thì sẽ có số điểm là[40,100,300,1200], vì công thức tính điểm chơi ưu tiên clear nhiều linemột lúc, có thể mang lại nguy hiểm cho AI vì độ cao trung bình sẽ đượcđẩy lên để ưu tiên clear nhiều line AI với fitness theo số line đã clear thì
sẽ ưu tiên clear line trước, dẫn đến độ cao trung bình thấp, khiến chúngsống được lâu hơn
Trang 221▪ Cần có kiểm chứng về thực lực của các AI với các bộ fitness khác nhau
Trang 22216
Trang 223ĐỒ ÁN 1
Trang 224TETRIS AI
Trang 225Đặc điểm:
Trang 226Case
#4: AI tự train vs AI tự train
Trang 227▪ Khi so sánh với nhau các AI sẽ được so sánh bằng số điểm đạt được
▪ Các viên gạch và thứ tự gạch xuất hiện được xác định bằng seed ▪ 2
AI sẽ được train ở seed = 1
Trang 228▪ Ta sẽ train 2 AI đối đầu với nhau
Trang 229Fitness của AI 1 được đo bằng:
Trang 230số lượng line đã xóa*1000 + số lượng gạch đã đặt
Trang 231Kết quả huấn luyện AI 1 ( record4 )
Trang 232Max fitness
Weight của cá thể tốt nhất
5 5 2 300 119,300 [-0.40254199, 0.83214954,
-1.22852379, - 0.60817082]
Trang 235Fitness của AI 2 được đo bằng số điểm đạt được
Trang 236Kết quả huấn luyện AI 2 ( record5 )
Trang 237Max fitness
Weight của cá thể tốt nhất
5 5 2 300 59,075 [-0.99519968, 0.51426722,
-1.50641223, - 0.66819176]
Trang 240Bảng so sánh
Trang 241Lưu ý:
Trang 242▪ Với các bài train ở số lượng gạch là vô hạn thì chúng sẽ được giám sát
kèm với UI nên số điểm có thể thấp hơn số điểm tối đa mà chúng có thể đạt được
Trang 243STT seed Số lượng
gạch ScoreAI 1 ScoreAI 2 score/AI 2AI 1
score4.1 1 100 7,551 7,620 0.9909448824.2 1 300 55,769 59,075 0.9440372414.3 1 600 203,548 225,613 0.9021997854.4 1 Infinite 2,134,8
32(lost)
495,705 (lost)
4.306658194
Trang 24617
Trang 247ĐỒ ÁN 1
Trang 248TETRIS AI
Trang 2494.5 2 Infinite 337,842
(lost)
1,550,578 (lost)
0.217881332
Trang 252Phân tích case #4:
Trang 253▪ AI của chúng ta được huấn luyện qua 5 đời, mỗi đời gồm 5 cá thể, với giới hạn gạch là 300
Trang 254▪ Khi train AI sử dụng số điểm đạt được AI sẽ tối ưu clear nhiều hàng cungmột lúc hơn, như ở 4.1 và 4.2 và 4.3, AI 2 luôn đạt điểm số cao hơn ▪ Nhưng khi muốn AI tồn tại lâu hơn thì AI 1 tồn tại tốt hơn (4.4)
Trang 255Kết luận case #4:
Trang 256▪ AI được huấn luyện với fitness ưu tiên clear nhiều hàng một lúc (AI 2)
sẽ có hiệu năng tốt hơn trong số lượng gạch được cho phép ▪ AI được huấn luyện với fitness ưu tiên độ cao của các cột (AI 1) sẽ có khả năng tồn tại tốt hơn (4.4)
Trang 257▪ Nếu muốn đánh giá AI bằng các seed khác nhau thì nên thử ở nhiều seed vì kết quả sẽ phụ thuộc vào seed đó (4.5)
Trang 25818
Trang 259ĐỒ ÁN 1 TETRIS AI
Trang 260KẾT LUẬN
Trang 261Sau các bài nghiên cứu và huấn luyện thì nhóm đã có nhiều kiến thức và đãtạo được một AI có khả năng chơi Tetris hơn 34,000,000 điểm AI được huấnluyện qua 20 đời, gồm 20 cá thể, 5000 gạch giới hạn và được huấn luyệnhoàn chỉnh trong vòng 13h (Video)
Trang 262Sau khi hoàn thành đồ án và tiến hành các bài nghiên cứu, nhóm đã biết thêmđược nhiều nội dung liên quan đến AI nói chung và AI cho game Tetris nóiriêng
Trang 263Các kiến thức trên có thể được dùng cho học tập và các dự án sau này, nhómrất vui vì đã chọn được một đề tài tốt và mang lại nhiều kiến thức bổ ích
Trang 264Cảm ơn các thầy cô hoặc các bạn đã đọc qua báo cáo.
Trang 26519
Trang 266ĐỒ ÁN 1 TETRIS AI
Trang 267THAM KHẢO
Trang 268Tetris game:
Trang 269b a36f6fcc9
Trang 270Demo weights & charts: Tetris AI – The (Near) Perfect Bot | Code My Road (wordpress.com)