Caro (hay còn gọi là Gomoku) là trò chơi chiến thuật đặt quân có lịch sử lâu đời ở châu Á, nhưng ở dạng kỹ thuật số hiện đại, nó đặt ra những thách thức lập trình rất thú vị: làm sao để AI chơi đủ thông minh mà không cần huấn luyện một mạng nơ-ron phức tạp? Đây là câu hỏi tôi đặt ra khi xây dựng Caro Arena — một nền tảng chơi Caro multiplayer thời gian thực, nơi người chơi có thể đối đầu với nhau hoặc thách đấu với một đối thủ AI được hỗ trợ bởi Gemini API của Google. Bài viết này ghi lại toàn bộ hành trình thiết kế hệ thống đó.

Vì sao dùng Generative AI thay vì thuật toán truyền thống?

Cách tiếp cận cổ điển để xây dựng AI Caro là dùng thuật toán Minimax với Alpha-Beta pruning — một kỹ thuật tìm kiếm cây trò chơi đã được chứng minh hiệu quả. Tuy nhiên, cách này đòi hỏi bạn phải tự định nghĩa hàm đánh giá thế cờ (evaluation function), xác định các mẫu nguy hiểm như "bốn đầu hở", "ba đầu hở", v.v. Với Gemini API, bạn có thể mô tả trạng thái bàn cờ bằng ngôn ngữ tự nhiên và để model suy luận nước đi tiếp theo — điều này mở ra khả năng AI "giải thích" lý do nó đi nước đó, tạo trải nghiệm học tập thú vị cho người chơi.

Prompt Engineering cho bàn cờ Caro

Thách thức lớn nhất khi dùng LLM cho bài toán game là đảm bảo output có cấu trúc và hợp lệ. Bàn cờ 15x15 không thể hiển thị tốt trong văn bản thuần túy. Giải pháp là serialize bàn cờ thành ma trận ký tự và yêu cầu model trả về tọa độ JSON:

async function getAIMove(board, playerSymbol) {
  const boardStr = board
    .map((row, r) => row.map((cell, c) => cell || '.').join(' '))
    .join('\n');

  const prompt = `
Ban dang choi Caro (Gomoku) tren ban co 15x15.
Nguoi choi la '${playerSymbol}', AI la '${playerSymbol === 'X' ? 'O' : 'X'}'.
Trang thai hien tai (. = trong, X = nguoi, O = AI):

${boardStr}

Hay chon nuoc di tot nhat cho AI de thang hoac ngan nguoi choi thang.
Chi tra ve JSON: {"row": <0-14>, "col": <0-14>}
Khong giai thich them.
  `.trim();

  const response = await genAI
    .getGenerativeModel({ model: 'gemini-2.0-flash' })
    .generateContent(prompt);

  const text = response.response.text().trim();
  const match = text.match(/\{.*\}/s);
  if (!match) throw new Error('AI response khong hop le');
  return JSON.parse(match[0]);
}

Matchmaking thời gian thực với Firestore

Phần multiplayer được xây dựng trên Firebase Firestore với mô hình lắng nghe sự kiện realtime. Mỗi ván cờ là một document trong collection games, chứa trạng thái bàn cờ, ID của hai người chơi, lượt hiện tại, và trạng thái ván đấu. Khi người dùng nhấn "Tìm trận", ứng dụng sẽ kiểm tra collection waitingRoom — nếu có người đang đợi, tạo game mới và ghép đôi; nếu không, thêm người chơi vào hàng đợi. Cả hai phía đều lắng nghe document game qua onSnapshot, đảm bảo mỗi nước đi được phản ánh tức thì trên cả hai màn hình mà không cần polling.

Bài học từ thực tế: Gemini API có độ trễ khoảng 1–3 giây mỗi lần gọi. Trong trò chơi, điều này cảm giác khá chậm. Giải pháp là hiển thị animation "AI đang suy nghĩ..." và giới hạn thời gian suy nghĩ tối đa 10 giây — nếu quá hạn, fallback về thuật toán heuristic đơn giản.

Quản lý lượt chơi và phát hiện chiến thắng

Logic quản lý lượt được xử lý hoàn toàn phía server thông qua Firebase Cloud Functions để tránh gian lận. Sau mỗi nước đi, Cloud Function kiểm tra 4 hướng (ngang, dọc, hai đường chéo) xem có chuỗi 5 quân liên tiếp không. Nếu có, cập nhật trường status: 'finished'winner vào document game. Client lắng nghe thay đổi này và hiển thị kết quả. Đặc biệt, chế độ chống nước đi hợp lệ (chỉ cho phép đặt quân vào ô trống, không cho phép đặt khi không phải lượt mình) cũng được validate server-side, tạo ra một hệ thống đáng tin cậy hơn so với validate thuần client.

Kết quả và hướng phát triển tiếp theo

Caro Arena hiện có tỉ lệ chiến thắng của AI ở mức khoảng 60–65% khi đối đầu với người chơi phổ thông — không phải vô địch, nhưng đủ thách thức để trải nghiệm thú vị. Hướng cải tiến tiếp theo bao gồm: fine-tuning prompt để AI tập trung vào phòng thủ hơn, thêm chế độ AI với các mức độ khó khác nhau, và tích hợp tính năng "phân tích ván đấu" hậu game nơi Gemini giải thích những nước đi then chốt. Việc dùng một LLM cho logic game mở ra cánh cửa cho những trải nghiệm tương tác phong phú mà AI thuật toán truyền thống không thể cung cấp.