Làm Game Othello - Cờ Đen Trắng Chơi Với Máy

Làm Game Othello - Cờ Đen Trắng Chơi Với Máy

Trò chơi cờ trắng đen (Othello) chắc hẳn không phải là xa lạ với nhiều người, cũng có thể là một trò chơi khá mới đối với các bạn trẻ.

Nguồn gốc về trò chơi này được biết đến với hai giả thuyết khác nhau. Vào thế kỷ 19, có một người đã phát minh ra trò chơi này và một nhà xuất bản chuyên về các loại trò chơi nổi tiếng của nước Đưc là Ravensburger bắt đầu tạo ra vào năm 1898 như là một trong những tít đầu tiên.

Trò chơi này được lấy tên từ vở kịch Othello, the Moor of Venice của William Shakespeare.

Chơi như thế nào?

Trò chơi được chia thành các ô vuông, Hai người chơi sẽ chọn một màu của họ và đánh nó suốt trận đấu, khởi tạo sẽ bằng là một bên 2 quân cờ.

Hai người sẽ đánh xen kẽ nhau. Nếu các quân cờ của đối thủ bị bao vây bởi 2 quân cờ của mình trên hàng dọc, hàng ngang hoặc hai đường chéo thì tất cả những quân bị bao vây đó sẽ biến thành quân cờ của mình.

Trò chơi kết thúc khi không còn chỗ để đánh, ai có số lượng quân cờ nhiều hơn 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

Giao diện của trò chơi rất đơn giản, giống với các bài hướng dẫn làm game như caro, dò mìn, ... mình cũng tạo giao diện bằng Java Swing.

Dùng một matrix button để khởi tạo ra các ô vuông.

Phía dưới là tất cả những thứ mà mình đã dùng để tạo ra giao diện hoàn chỉnh.

	public Container init() {
		Container cn = this.getContentPane();
		pn = new JPanel();
		pn.setLayout(new GridLayout(m, n));
		for (int i = 0; i < m ; i++)
			for (int j = 0; j < n; j++){
				bt[i][j] = new JButton("   ");
				a[i][j] = 2;
			}
		for (int i = 0; i < m; i++)
			for (int j = 0; j < n; j++)
			{
				bt[i][j] = new JButton("   ");
				pn.add(bt[i][j]);
				bt[i][j].setActionCommand(i + " " + j);
				bt[i][j].setBackground(background_cl);
				bt[i][j].addActionListener(this);
			}
		for (int i = m / 2 - 1; i <= m / 2; i++)
			for (int j = n / 2 - 1; j <= n / 2; j++) {
				bt[i][j].setBackground(background[(i + j) % 2]);
				a[i][j] = (i + j) % 2;
			}
		count1 = new JButton("2");
		count1.setBackground(background[0]);
		count1.setFont(new Font("Arial", 1, 20));
		count1.setForeground(Color.white);
		count2 = new JButton("2");
		count2.setBackground(background[1]);
		count2.setFont(new Font("Arial", 1, 20));
		count2.setForeground(Color.white);
		pn2 = new JPanel();
		pn2.setLayout(new FlowLayout());
		pn2.add(count1);
		pn2.add(count2);
		cn.add(pn);
		cn.add(pn2, "South");
		this.setVisible(true);
		this.setSize(m * 50, n * 50);
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		this.setLocationRelativeTo(null);
		return cn;
	}

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

Trò chơi này không cần quá nhiều dữ liệu:

	boolean win = false;
	private Color background_cl = Color.gray;
	private Color background[] = {Color.black, Color.green};
	private int m = 10, n = 10, count = 0;
	private int a[][] = new int[m][n];
	private JButton bt[][] = new JButton[m][n];
	private JButton count1, count2;

Bài này có điều đặc biệt hơn là mình có sử dụng class Timer, để dùng hỗ trợ cho việc lập trình Bot (sẽ nói ở bên dưới).

Các hàm xử lý

1. Đánh thêm một quân cờ

Khi đánh thêm 1 quân cờ chúng ta cần phải kiểm tra các đường chéo, các hàng các cột xem nước đi đó có thể ăn được các quân cờ của đối thủ hay không.

	public void solve(int x, int y) {
		addPoint(x, y);
		int i, j, h;
		i = x; j = y + 1;
		while (j < n && a[i][j] == 1 - count)
			j++;
		if (j < n && a[i][j] == count)
			for (int k = y + 1; k < j; k++) {
				a[i][k] = count;
				bt[i][k].setBackground(background[count]);
			}
		i = x; j = y - 1;
		while (j >= 0 && a[i][j] == 1 - count)
			j--;
		if (j >= 0 && a[i][j] == count)
			for (int k = j + 1; k < y; k++) {
				a[i][k] = count;
				bt[i][k].setBackground(background[count]);
			}
		
		i = x + 1; j = y;
		while (i < m && a[i][j] == 1 - count)
			i++;
		if (i < m && a[i][j] == count)
			for (int k = x + 1; k < i; k++) {
				a[k][j] = count;
				bt[k][j].setBackground(background[count]);
			}
		i = x - 1; j = y;
		while (i >= 0 && a[i][j] == 1 - count)
			i--;
		if (i >= 0 && a[i][j] == count)
			for (int k = i + 1; k < x; k++) {
				a[k][j] = count;
				bt[k][j].setBackground(background[count]);
			}
		
		i = x + 1; j = y + 1;
		while (i < m && j < n && a[i][j] == 1 - count) {
			i++;
			j++;
		}
		h = y + 1;
		if (i < m && j < n && a[i][j] == count)
			for (int k = x + 1; k < i; k++, h++) {
				a[k][h] = count;
				bt[k][h].setBackground(background[count]);
			}
		i = x - 1; j = y - 1;
		while (i >= 0 && j >= 0 && a[i][j] == 1 - count) {
			i--;
			j--;
		}
		h = j + 1;
		if (i >= 0 && j >= 0 && a[i][j] == count)
			for (int k = i + 1; k < x; k++, h++) {
				a[k][h] = count;
				bt[k][h].setBackground(background[count]);
			}
		
		i = x + 1; j = y - 1;
		while (i < m && j >= 0 && a[i][j] == 1 - count) {
			i++;
			j--;
		}
		h = j + 1;
		if (i < m && j >= 0 && a[i][j] == count)
			for (int k = i - 1; h < y; k--, h++) {
				a[k][h] = count;
				bt[k][h].setBackground(background[count]);
			}
		i = x - 1; j = y + 1;
		while (i >= 0 && j < n && a[i][j] == 1 - count) {
			i--;
			j++;
		}
		h = y + 1;
		if (i >= 0 && j < n && a[i][j] == count)
			for (int k = x - 1; h < j; k--, h++) {
				a[k][h] = count;
				bt[k][h].setBackground(background[count]);
			}	
	}

2.  Kiểm tra kết thúc trò chơi

Trò chơi kết thúc khi không còn ô nào có thể đánh được nữa.

Người nào có nhiều quân cờ hơn sẽ thắng.

	public void checkWin() {
		boolean kt = true;
		for (int i = 0; i < m; i++)
			for (int j = 0; j < n; j++)
				if (a[i][j] == 2)
					kt = false;
		if (kt) {
			win = true;
			timer.stop();
			String s = "Đen";
			if (Integer.parseInt(count2.getText()) > Integer.parseInt(count1.getText()))
				s = "Xanh";
			JOptionPane.showMessageDialog(null, s + " đã chiến thắng!");
		}
	}

3. Update số lượng quân cờ hai bên

Chúng ta sẽ kiểm tra tất cả các quân cờ trên bàn cờ và đếm số lượng quân của hai bên.

	public void update() {
		int k1 = 0, k2 = 0;
		for (int i = 0; i < m; i ++)
			for (int j = 0; j <n; j++) {
				if (a[i][j] == 0)
					k1++;
				if (a[i][j] == 1)
					k2++;
			}
		count1.setText(String.valueOf(k1));
		count2.setText(String.valueOf(k2));
		count = 1 - count;
		checkWin();
	}

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

1. Auto tạo đánh cờ ngẫu nhiên

Để lập trình cho máy đánh một quân cờ ngẫu nhiên, thường dùng khi không có bước đi nào mà Bot có thể ăn được quân cờ của mình.

Tạo một quân cờ ngẫu nhiên, bất kỳ mà chưa được đánh trước đó.

	int[] creatPoint() {
		int arr[] = new int[2];
		int k = 0;
		for (int i = 0; i < m; i++)
			for (int j = 0; j < n; j++)
				if (a[i][j] == 2)
					k++;
		int h = (int) (Math.random() * (k - 1)) + 1;
		k = 0;
		for (int i = 0; i < m; i++)
			for (int j = 0; j < n; j++)
				if (a[i][j] == 2) {
					k++;
					if (k == h) {
						arr[0] = i;
						arr[1] = j;
					}
				}
		return arr;
	}
	public void addPoint(int i, int j) {
		if (a[i][j] == 2) {
			a[i][j] = count;
			bt[i][j].setBackground(background[count]);
		}
	}

Tuy nhiên chúng ta cần tạo thêm một hàm nữa, vì nếu tạo ngẫu nhiên như vậy sẽ giảm độ khó của game khi mà Bot không biết "chiếm góc", nên ta phải viết hàm cho Bot tạo ngẫu nhiên một quân cờ ở trong góc và viền ngoài của bàn cờ.

	int[] creatBoder() {
		int arr[] = new int[2];
		arr[0] = arr[1] = -1;
		int k = 0;
		for (int i = 0; i < m; i++) {
			if (a[i][0] == 2)
				k++;
			if (a[i][n - 1] == 2)
				k++;
		}
		for (int j = 0; j < n; j++) {
			if (a[0][j] == 2)
				k++;
			if (a[m - 1][j] == 2)
				k++;
		}
		int h = (int) (Math.random() * (k - 1)) + 1;
		k = 0;
		for (int i = 0; i < m; i++) {
			if (a[i][0] == 2) {
				k++;
				if (h == k) {
					arr[0] = i;
					arr[1] = 0;
				}
			}
			if (a[i][n - 1] == 2) {
				k++;
				if (h == k) {
					arr[0] = i;
					arr[1] = n - 1;
				}
			}
		}
		for (int j = 0; j < n; j++) {
			if (a[0][j] == 2) {
				k++;
				if (h == k) {
					arr[0] = 0;
					arr[1] = j;
				}
			}
			if (a[m - 1][j] == 2) {
				k++;
				if (h == k) {
					arr[0] = m - 1;
					arr[1] = j;
				}
			}
		}
		return arr;
	}

2. Tối ưu hóa bước đánh

Để tối ưu hóa mỗi bước đánh ta sẽ xét tất cả những bước có thể đánh được, bước nào có thể ăn được nhiều nhất, ta sẽ cho Bot đánh chỗ đó.

	public void auto() {
		int  arr[] = {-1, -1};
		if (Math.random() > 0.7)
			arr = creatBoder();
		if (arr[0] == -1) arr = creatPoint();
		int k = arr[0];
		int h = arr[1];
		int M = 0;
		for (int i = 0; i < m; i++)
			for (int j = 0; j < n; j++)
			if (a[i][j] == 2){
				int temp = countSolve(i, j);
				if (temp > M) {
					M = temp;
					k = i;
					h = j;
				}
			}
		if (M > 0) {
			solve(k, h);
		}
		else {
			solve(k, h);
		}
	}

Hàm coutSovle(x, y) chính là số lượng quần cờ có thể ăn khi đánh ở ô (x, y)

3. Triển khai Bot

Cứ sau mỗi bước đánh của mình thì Bot sẽ đánh duy nhất một bước đánh.

		timer = new Timer(0, new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (!win) {
					auto();
					update();
					timer.stop();
				}
			}
		});

Kết

Trên đây là cách tạo ra trò chơi Othello 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: