Mình đã làm bot tự chơi cờ tướng online như thế nào?
Repo: https://github.com/DuongStark/XiangqiAutoBot
Mình làm project này vì tò mò một câu khá đơn giản:
Nếu chỉ có một trang web cờ tướng đang mở trong Chrome, liệu extension có tự nhìn được bàn cờ, hiểu thế cờ, hỏi engine, rồi tự kéo quân đi không?
Nghe thì giống kiểu chỉ là gọi engine lấy best move là xong, nhưng làm rồi mới thấy phần hay không nằm ở engine nhiều như mình nghĩ.
Engine mạnh thì có sẵn rồi. Pikafish đánh cờ tướng rất khỏe. Phần mệt hơn là làm sao để Chrome extension hiểu được cái bàn cờ đang hiển thị trên web:
- quân nào đang nằm ở ô nào
- bàn đang xoay theo bên đỏ hay bên đen
- đổi thế cờ trên web thành FEN đúng chuẩn cờ tướng
- gửi FEN đó cho engine local
- nhận nước đi
- rồi kéo quân trên web như người dùng thật
Nói ngắn gọn, đây là một project học về browser automation, Chrome extension, UCI engine và cách nối mấy thứ tưởng không liên quan lại với nhau.
Mình nói trước cho rõ: project này nên dùng để học và test local, phân tích thế cờ, hoặc đánh với máy. Không nên dùng engine assistance để đánh với người thật. Vừa không vui, vừa gây trải nghiệm khó chịu cho người chơi khác.
Tổng quan project
Luồng chính của bot hiện tại như này:
play.xiangqi.com
-> content.js đọc bàn cờ từ DOM
-> tạo Xiangqi FEN
-> gọi server local http://127.0.0.1:8080/bestmove
-> server.py nói chuyện với Pikafish qua UCI
-> Pikafish trả bestmove
-> extension highlight hoặc tự kéo quân
Mình tách làm 2 phần:
| Phần | Nhiệm vụ |
|---|---|
| Chrome extension | Đọc bàn cờ, điều khiển UI, auto move, popup |
| Python server | Chạy local, giữ Pikafish sống, nhận FEN và trả nước đi |
Lý do không nhét engine vào extension luôn là vì Chrome extension không tiện chạy file .exe trực tiếp. Pikafish là engine native, nên cách gọn nhất là để một server Python local đứng giữa.
Ban đầu mình cũng nghĩ tới Chrome Native Messaging để bấm Start trong extension là tự bật được Python/Pikafish luôn. Hướng đó trải nghiệm đẹp hơn thật, nhưng đổi lại phải cài native host vào Windows Registry, viết installer, xử lý permission, path, update... Với một project nhỏ thì nó hơi nặng.
Cuối cùng mình chọn hướng dễ chạy hơn:
1. chạy python server.py
2. mở extension
3. bấm Start bot
Dùng như này thì dễ hiểu hơn, dễ debug hơn, và ae clone repo về chạy cũng đỡ sợ mấy đoạn cài registry.
Cấu trúc file
Project hiện tại đại khái như này:
xiangqi-bot/
|-- assets/
|-- icons/
|-- engine/
|-- content.js
|-- download_engine.py
|-- manifest.json
|-- popup.html
|-- popup.js
|-- server.py
|-- README.md
|-- LICENSE
`-- .gitignore
Trong đó mấy file chính là:
| File | Làm gì |
|---|---|
content.js |
Chạy trong trang play.xiangqi.com, đọc bàn, tạo FEN, auto move |
server.py |
HTTP server local, wrap Pikafish bằng UCI |
popup.html |
Giao diện extension |
popup.js |
Xử lý Start/Stop, mode, engine time, skill |
download_engine.py |
Tải Pikafish |
manifest.json |
Khai báo Chrome extension MV3 |
Đọc bàn cờ từ web
Đây là phần mình thấy thú vị nhất.
Trên web thì họ không cho sẵn object kiểu:
board[rank][file] = piece
Thứ mình có là DOM. Tức là mấy element HTML, ảnh quân cờ, grid bàn cờ, vị trí hiển thị trên màn hình.
Nên cách làm là:
- tìm element bàn cờ,
- lấy kích thước bàn,
- chia bàn thành 10 hàng x 9 cột,
- lấy vị trí từng quân cờ,
- gán quân vào ô gần nhất.
Ý tưởng đơn giản:
lấy center của từng ô
lấy center của từng quân
quân nào gần ô nào nhất thì coi như nằm ở ô đó
Mình không phụ thuộc quá nhiều vào class name phức tạp của web, vì class có thể đổi. Đọc bằng tọa độ thường bền hơn trong mấy project kiểu này.
Bàn cờ có thể bị xoay
Một vấn đề khá dễ nhầm là bàn cờ trên web không phải lúc nào cũng quay về cùng một hướng.
Ví dụ ae cầm đỏ thì thường thấy quân đỏ ở phía dưới. Nhưng nếu ae cầm đen, bàn có thể bị xoay lại, lúc đó quân đen mới nằm phía dưới.
Với mắt người thì chuyện này rất bình thường. Nhưng với bot thì lại nguy hiểm. Vì bot chỉ đang đọc tọa độ trên màn hình:
hàng trên màn hình
hàng dưới màn hình
cột trái
cột phải
Nếu bot tưởng "phía dưới màn hình" luôn là phía đỏ, trong khi bàn đang xoay cho bên đen, toàn bộ thế cờ sẽ bị đọc ngược.
Cách mình xử lý là nhìn quân tướng.
Trong cờ tướng, hai con tướng nằm ở hai đầu bàn cờ. Con nào đang nằm thấp hơn trên màn hình thì bên đó là phía người chơi.
nếu tướng đỏ nằm dưới màn hình -> bot hiểu bàn đang quay theo phía đỏ
nếu tướng đen nằm dưới màn hình -> bot hiểu bàn đang quay theo phía đen
Sau khi biết bàn đang quay theo phía nào, bot mới đổi tọa độ màn hình sang ô cờ thật.
Nói gọn hơn:
tọa độ trên màn hình != tọa độ thật của bàn cờ
phải biết bàn đang xoay hướng nào trước
rồi mới build FEN cho engine
Đoạn này nghe nhỏ, nhưng nếu sai thì engine sẽ phân tích một thế cờ khác hoàn toàn. Nước trả về nhìn sẽ rất vô lý.
Chuyển sang Xiangqi FEN
Sau khi biết quân nào ở ô nào, mình cần đổi sang FEN để engine hiểu.
Ví dụ dạng FEN cờ tướng sẽ trông kiểu:
rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1
Trong đó:
- chữ hoa là quân đỏ,
- chữ thường là quân đen,
- số là ô trống liên tiếp,
whoặcblà bên tới lượt.
Chỗ "bên tới lượt" cũng không phải lúc nào web cho mình sẵn. Nên mình dùng trạng thái bàn trước đó để suy luận. Nếu bàn vừa thay đổi sau một nước của đối thủ, bot biết đã tới lượt mình và bắt đầu hỏi engine.
Đây là kiểu logic nghe hơi thủ công, nhưng với browser automation thì khá thực tế. Không phải web nào cũng cho mình API sạch để dùng.
Python server và Pikafish
Pikafish là engine UCI. Mình không gọi nó kiểu mỗi request bật một process mới, vì như vậy rất chậm.
Thay vào đó server.py bật engine một lần, giữ process sống, rồi gửi lệnh qua stdin/stdout.
Luồng UCI cơ bản:
uci
isready
position fen ...
go movetime 1000
bestmove h2e2
Extension gọi server bằng HTTP:
{
"fen": "...",
"movetime": 1000,
"plies": 18,
"style": "sparring",
"skill": 4
}
Server trả về kiểu:
{
"bestmove": "h2e2"
}
Lúc đầu mình tưởng tăng thời gian nghĩ lên nhiều là bot sẽ giống người chơi thật hơn. Nhưng test xong mới thấy không hẳn.
Có những thế, Pikafish nghĩ 1 giây đã ra nước rất mạnh rồi. Cho nó 10 giây hay 20 giây đôi khi không làm nó giống người hơn, mà chỉ làm người dùng phải chờ lâu hơn. Tệ hơn nữa, nếu mình cố tình chọn nước phụ quá rộng thì bot có thể đánh ngu một cách không tự nhiên.
Vì vậy hiện tại mình tách ra 2 mode rõ ràng.
Fast mode
Fast mode là đường thẳng nhất:
đọc bàn -> hỏi engine -> có kết quả là đi luôn
Mode này gửi:
style: "best"
Fast mode bỏ qua Skill slider. Nó dùng engine theo hướng mạnh nhất trong thời gian đã chọn.
Nếu ae muốn bot đánh khỏe hơn, tăng Engine time lên. Nhưng theo mình thấy:
| Engine time | Cảm giác |
|---|---|
0.5s |
Nhanh, đủ dùng ở nhiều thế |
1s |
Đã khá mạnh |
2s - 5s |
Tốt hơn ở trung cuộc/tàn cuộc có chiến thuật |
Không phải cứ tăng lên 30 giây là trải nghiệm tốt hơn. Với cờ nhanh 10 phút, chờ lâu như vậy rất phí.
Human-like mode
Human-like mode sinh ra vì Fast mode nhìn sẽ dễ bị phát hiện là bot.
Nếu ván nào cũng:
- toàn best move,
- accuracy 97-99,
- gần như không có nước yếu,
- thời gian đi đều đều,
thì nhìn rất dễ nghi.
Nhưng mình cũng không muốn làm bot "giả ngu" kiểu random bừa. Đánh sai lộ liễu cũng không giống người. Người thật thường không tự nhiên bỏ quân, nhưng có thể chọn một nước tốt thứ 2 hoặc thứ 3 nếu mấy nước đó gần ngang nhau.
Nên Human-like mode làm theo hướng:
engine phân tích nhiều PV
lọc các nước có điểm không quá lệch
chọn trong nhóm nước vẫn ổn
thêm delay theo giai đoạn ván cờ
Nói đơn giản: không phải nước nào cũng PV1, nhưng vẫn tránh mấy nước quá tệ.
Delay theo giai đoạn
Mình không để delay một con số cố định nữa.
Khai cuộc thì thường đi nhanh hơn. Trung cuộc thì chậm lại vì nhiều tactic. Tàn cuộc cũng cần chính xác hơn. Nếu còn dưới 1 phút thì phải đánh nhanh, không thể ngồi nghĩ như còn 10 phút.
Luồng delay kiểu:
| Giai đoạn | Cách đánh |
|---|---|
| Khai cuộc | nhanh hơn |
| Trung cuộc | chậm vừa |
| Tàn cuộc | chậm hơn nếu còn thời gian |
| Gần hết giờ | ưu tiên đi nhanh |
Đây không phải mô phỏng người hoàn hảo, nhưng tự nhiên hơn kiểu cứ đúng 5 giây mới đi.
Skill slider
Sau khi test Human-like mode, mình gặp một vấn đề: nếu cho random quá rộng, bot yếu đi nhanh và đánh hơi kỳ.
Vì vậy mình thêm Skill slider.
Skill chỉ ảnh hưởng Human-like mode:
| Skill | Hành vi |
|---|---|
1 |
rộng hơn, nhiều variety hơn, yếu hơn |
3 |
cân bằng |
5 |
hẹp hơn, gần best move hơn |
Fast mode không dùng Skill.
Cái này quan trọng vì người dùng có hai nhu cầu khác nhau:
- muốn engine mạnh thì dùng Fast,
- muốn đánh sparring tự nhiên hơn thì dùng Human-like và chỉnh Skill.
Auto move bằng drag/drop
Sau khi có bestmove như:
h2e2
Extension cần biến nó thành hành động trên web.
Không phải web nào cũng cho click 2 ô là đi. Có trang cần drag quân từ ô này sang ô kia. Nên mình giả lập chuỗi event kiểu người kéo quân:
mousedown
mousemove
mouseup
Phần này khá nhạy. Nếu tọa độ sai một chút, hoặc element bị overlay, hoặc animation chưa xong, nước có thể không đi.
Vì vậy trong popup mình để cả Start và Stop. Nếu bot bị đứng hoặc web đổi state, người dùng có thể:
Stop bot -> Start bot
Ghi chú này mình cho hiện thẳng trong extension, vì thực tế dùng extension kiểu này rất cần nút reset nhanh.
Giao diện extension
Các trạng thái chính:
| Control | Ý nghĩa |
|---|---|
Start bot |
bắt đầu đọc bàn và chạy bot |
Stop bot |
dừng bot |
Auto move |
bật/tắt tự kéo quân |
Fast |
đi ngay khi engine trả kết quả |
Human-like |
thêm delay và chọn nước có variety |
Engine time |
thời gian engine phân tích mỗi lần |
Skill |
độ sắc của Human-like mode |
Popup trạng thái trên trang cũng được sửa lại để cùng style với extension, không còn cảm giác ghép tạm.
Setup để chạy thử
Clone repo:
git clone https://github.com/DuongStark/XiangqiAutoBot
cd XiangqiAutoBot
Tải engine:
python download_engine.py
Chạy server:
python server.py
Load extension:
1. mở chrome://extensions
2. bật Developer mode
3. chọn Load unpacked
4. chọn folder repo
Sau đó vào:
https://play.xiangqi.com
Mở bàn cờ, bấm extension, rồi Start bot.
Nếu không thấy bot đi:
Stop bot -> Start bot
Nếu vẫn không được thì refresh trang hoặc kiểm tra server Python có đang chạy không.
Mấy thứ mình rút ra
1. Engine mạnh không phải phần khó nhất
Trước khi làm mình nghĩ bot chơi cờ khó nhất chắc là engine.
Nhưng với project này, engine là phần ổn nhất vì Pikafish đã quá mạnh. Phần khó hơn là bridge:
web UI <-> extension <-> local server <-> native engine
Chỉ cần một đoạn trong chain sai là mọi thứ hỏng.
2. Delay lâu không làm bot giống người hơn
Mình từng thử để bot deep search 10-30 giây. Kết quả là không thuyết phục lắm.
Ở nhiều thế, 1 giây đã đủ ra nước rất mạnh. Nghĩ thêm chỉ làm trải nghiệm chậm. Nếu sau đó lại cố chọn nước yếu để giảm accuracy thì nhìn càng kỳ.
Hướng hợp lý hơn là:
- Fast mode thì cứ mạnh và nhanh,
- Human-like mode thì chọn trong nhóm nước tốt gần nhau,
- delay thay đổi theo phase và thời gian còn lại.
3. Random bừa không giống người
Lúc muốn giảm độ "máy", cách dễ nhất là random. Nhưng random quá rộng thì bot đánh ngu.
Người thật không đánh kiểu mỗi vài nước lại tự phá thế. Nên variety cần có kiểm soát bằng centipawn gap, MultiPV và Skill.
Nếu làm tiếp
Nếu có thời gian mình muốn làm thêm mấy hướng này:
| Ý tưởng | Lý do |
|---|---|
| Native Messaging installer | Bấm Start trong extension là tự bật server |
| Review mode | Sau ván tự phân tích sai lầm |
| Local sparring board | Không phụ thuộc web bên ngoài |
| Opening book | Khai cuộc tự nhiên hơn |
| Better time control | Chia thời gian theo clock thật |
| Move explanation | Không chỉ đi nước, mà giải thích vì sao |
Trong mấy hướng này, mình thích Review mode nhất. Vì nó biến project từ "bot tự đánh" thành công cụ học cờ thật sự.
Kết
Project này bắt đầu từ một câu hỏi khá nghịch:
Chrome extension có tự nhìn bàn cờ và chơi cờ tướng được không?
Làm xong thì câu trả lời là có. Nhưng phần đáng học không chỉ là engine, mà là toàn bộ đường đi từ pixel/DOM trên web tới UCI engine local rồi quay lại thành một nước kéo quân trên trình duyệt.
Mình thấy đây là kiểu project rất hợp để học thực chiến vì nó đụng nhiều thứ cùng lúc:
- Chrome extension MV3,
- DOM geometry,
- browser automation,
- local HTTP server,
- UCI protocol,
- engine tuning,
- UX cho tool kỹ thuật.
Ae nào thích cờ tướng, thích automation, hoặc thích mấy project nối browser với tool local thì có thể nghía repo ở đây:
https://github.com/DuongStark/XiangqiAutoBot
Và nhắc lại lần nữa cho rõ: dùng để học, test local, phân tích, sparring. Đừng dùng để phá trải nghiệm của người chơi thật.
All Rights Reserved