0

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à:

  1. tìm element bàn cờ,
  2. lấy kích thước bàn,
  3. chia bàn thành 10 hàng x 9 cột,
  4. lấy vị trí từng quân cờ,
  5. 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,
  • w hoặc b là 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

Viblo
Let's register a Viblo Account to get more interesting posts.