Làm Game TicTacToe Chơi Với Máy Bằng Java

Làm Game TicTacToe Chơi Với Máy Bằng Java

Trò chơi TicTacToe là trò chơi quen thuộc tương tự trò chơi caro, tuy nhiên nó được giới hạn về kích thước của bàn cờ. Thông thường bàn cờ sẽ có kích thước 3x3.

Luật chơi: Hai người sẽ lần lượt đánh các chữ O hoặc X vào một ô vuông chưa được đánh, nếu người nào tạo được một hàng ngang, cột dọc, đường chéo gồm các ký tự của mình thì người đó sẽ thắng.

Thiết lập giao diện và dữ liệu cần thiết

1. Thiết lập giao diện

Phần giao diện chủ yếu là các Jbutton trong Java Swing, kế thừa từ class JFram

	public Container init() {
		Container cn = this.getContentPane();
		pn0 = new JPanel();
		pn0.setLayout(new FlowLayout());
		luot = new JLabel("Lượt của X");
		luot.setFont(new Font("UTM Micra", 1, 20));
		pn0.add(luot);
		pn = new JPanel();
		pn.setLayout(new GridLayout(3, 3));
		for (int i = 0; i <9; i++) {
			bt[i] = new JButton(" ");
			pn.add(bt[i]);
			bt[i].setActionCommand(String.valueOf(i));
			bt[i].setBackground(background_cl);
			bt[i].addActionListener(this);
			bt[i].setFont(new Font("Antique", 1, 120));
			a[i] = 2;
		}
	
		pn2 = new JPanel();
		pn2.setLayout(new FlowLayout());
		
		newGame_bt = new JButton("New game");
		newGame_bt.setForeground(Color.green);
		newGame_bt.addActionListener(this);
		newGame_bt.setFont(new Font("UTM Swiss 721 Black Condensed", 1, 18));
		newGame_bt.setActionCommand("-1");
		
		point1_bt = new JButton(String.valueOf(point1));
		point1_bt.setFont(new Font("UTM Nokia", 1, 25));
		
		point2_bt = new JButton(String.valueOf(point2));
		point2_bt.setFont(new Font("UTM Nokia", 1, 25));
		
		pn2.add(point1_bt);
		pn2.add(newGame_bt);
		pn2.add(point2_bt);
		
		cn.add(pn0, "North");
		cn.add(pn);
		cn.add(pn2, "South");
		this.setVisible(true);
		this.setSize(500, 570);
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		this.setLocationRelativeTo(null);
		count = countH[0] = countH[1] = 0;
		return cn;
	}

Bạn cũng có thể tạo thêm các button để lưu số điểm của hai bên hoặc là nút tạo mới một ván chơi mới.

2. Các dữ liệu cần thiết

Với bài này không cần quá nhiều dữ liệu.

	boolean win = false;
	String icon;
	int point1 = 0, point2 = 0;
	int start = 0;
	String str = "";
	int count;
	int countH[] = {0, 0};
	String text[] = {"X", "O"};
	private Color background_cl = Color.white;
	private Color number_cl[] = {Color.red, Color.blue};
	private JButton bt[] = new JButton[9];
	private JButton point1_bt, point2_bt;
	private JLabel luot;
	private int a[] = new int[9];
	private JButton newGame_bt;
	private JPanel pn0, pn, pn2;
	Container cn;
	Timer timer;

Với bài này mình có sử dụng class Timer để sử dụng lập trình cho Bot (sử hướng dẫn ở sau).

Các hàm xử lý

1. Hàm kiểm tra kết thúc trò chơi

Trò chơi kết thúc khi một trong hai người chơi đánh được một hàng, một cột, hay một đường chéo là các ký tự của mình.

	public int checkWin() {
		updateMatrix();
		if (a[0] != 2 && a[0] == a[1] && a[1] == a[2])
			return a[0];
		if (a[3] != 2 && a[3] == a[4] && a[4] == a[5])
			return a[3];
		if (a[6] != 2 && a[6] == a[7] && a[7] == a[8])
			return a[6];
		
		if (a[0] != 2 && a[0] == a[3] && a[3] == a[6])
			return a[0];
		if (a[1] != 2 && a[1] == a[4] && a[4] == a[7])
			return a[1];
		if (a[2] != 2 && a[2] == a[5] && a[5] == a[8])
			return a[2];
		
		if (a[0] != 2 && a[0] == a[4] && a[4] == a[8])
			return a[0];
		if (a[2] != 2 && a[2] == a[4] && a[4] == a[6])
			return a[2];
		for (int i = 0; i < 9; i++)
			if (a[i] == 2)
				return -1;
		return 2;
	}

Trong hàm trên, hàm checkWin() sẽ trả về 0 nếu "0" thắng, trả về 1 nếu "X" thắng, trả về 2 nếu hai người hòa nhau.

2. Thêm một bước đánh vào bàn cờ

Để thêm một ký tự vào bàn cờ ta phải đảm bảo rằng ô vuông mà bạn định đánh chưa có quân cờ nào (a[i] = 2).

Ta sẽ dựa vào một biến count (dùng để đếm số bước đánh trên bàn cờ) để biết được hiện tại là lượt đánh của X hay O.

	public void addPoint(int k) {	
		if (!win) {
			if (a[k] == 2) {
				a[k] = count;
				bt[k].setForeground(number_cl[count]);
				bt[k].setText(text[count]);
				countH[count]++;
				count = 1 - count;
			}
			if (count == 0)
				luot.setText("Lượt của X");
			else
				luot.setText("Lượt của O");
			if (checkWin() != -1) {
				String mess = "";
				int cw = checkWin();
				if (cw == 2) {
					mess = "Hòa!";
					point1++;
					point2++;
				}
				else {
					if (icon != text[count]) {
						mess = "Bạn đã thắng!";
						point1 += 10;
					}
					else {
						mess = "Bạn đã thua!";
						point2 += 10;
					}
				}
				win = true;
				JOptionPane.showMessageDialog(null, mess);
			}
		}
	}

Các hàm lập trình cho Bot

1. Đánh thêm một quân cờ để Bot có thể thắng.

Để tìm ra được bước đi mà Bot có thể thắng, ta sử dụng hàm sau:

	public int checkPoint1() {
		for (int i = 0; i < 9; i++)
			if (a[i] == 2) {
				a[i] = count;
				bt[i].setText(text[count]);
				if (checkWin() != -1) {
					a[i] = 2;
					bt[i].setText(" ");
					return i;
				}
				a[i] = 2;
				bt[i].setText(" ");
			}
		return -1;
	}

Hàm trả về -1 nếu không có bước đánh mà Bot có thể thắng.

2. Đánh một quân cờ để chặn bước thắng cua mình

Ta sẽ kiểm tra xem trong bàn cờ có nước đi nào mà nếu Bot không chặn là Bot sẽ thua hay không.

	public int checkPoint2() {
		for (int i = 0; i < 9; i++)
			if (a[i] == 2) {
				a[i] = 1 - count;
				bt[i].setText(text[1 - count]);
				if (checkWin() != -1) {
					a[i] = 2;
					bt[i].setText(" ");
					return i;
				}
				a[i] = 2;
				bt[i].setText(" ");
			}
		return -1;
	}

Hàm trả về -1 nếu không tìm thấy bước đi nguy hiểm.

3. Đánh một quân cờ ngẫu nhiên.

Ta sẽ lập trình cho Bot đánh một quân cờ ngẫu nhiên (chưa được đánh trong bàn cờ) bằng cách sau.

Tuy là ngẫu nhiên, nhưng để tăng độ khó cho trò chơi thì ta nên tạo ngẫu nhiên một quân cờ ở vị trí có thứ tự yêu tiên giảm dần là: Quân cờ ở giữa bàn cờ (a[4]), 4 quân cờ 4 góc (a[0], a[2], a[6], a[8]), các ô còn lại.

	public int creatPointRandom() {
		if (a[4] == 2)
			return 4;
		int k = 0;
		k += (a[0] == 2) ? 1 : 0;
		k += (a[2] == 2) ? 1 : 0;
		k += (a[6] == 2) ? 1 : 0;
		k += (a[8] == 2) ? 1 : 0;
		if (k > 0) {
			int h = (int) ((k - 1) * Math.random() + 1);
			k = 0;
			k += (a[0] == 2) ? 1 : 0;
			if (k == h)
				return 0;
			k += (a[2] == 2) ? 1 : 0;
			if (k == h)
				return 2;
			k += (a[6] == 2) ? 1 : 0;
			if (k == h)
				return 6;
			k += (a[8] == 2) ? 1 : 0;
			if (k == h)
				return 8;
		}
		for (int i = 0; i < 9; i++)
			if (a[i] == 2)
				k++;
		int h = (int) ((k - 1) * Math.random() + 1);
		k = 0;
		for (int i = 0; i < 9; i++)
			if (a[i] == 2) {
				k++;
				if (k == h)
					return i;
			}
		return 0;
	}

4. Chọn nước đi tối ưu

Bot sẽ ưu tiên đánh nước đánh mà nó có thể thắng trước, nếu không thể thắng, nó sẽ đánh vào ô nguy hiểm (ô mà nếu Bot không chặn thì Bot có thể thua), nếu không có ô nguy hiểm Bot mới đánh một ô ngẫu nhiên.

	public int autoPoint() {
		int k = checkPoint1();
		if (k != -1)
			return k;
		else {
			k = checkPoint2();
			if (k != -1) 
				return k;
			else
				return creatPointRandom();
		}
	}

Kết

Trên đây là cách tạo ra trò chơi TicTacToe bằng Java Swing, cũng như cách lập trình Bot để có thể đánh với mình. Mọi góp ý xin vui lòng để lại ở phần bình luận.

Các bạn có thể tham khảo code của mình Tại đây.

Nếu thấy hay thì cho mình xin một share và rate 5* nhé. Cảm ơn các bạn đã theo dõi.

Video Demo: