Làm Game Dò Mìn Bằng Java (Phần 2)

Làm Game Dò Mìn Bằng Java (Phần 2)

Chào mừng các bạn quay trở lại với phần 2 của lập trình game dò mìn. Ở phần này, mình sẽ hướng dẫn làm game dò mìn hoàn chỉnh, có full source code ở cuối bài viết nhé.

Các điều cần thiết trong trò chơi dò mìn.

1. Đếm thời gian chơi

Để tăng tính kịch tính cho trò chơi, chúng ta sẽ tạo ra bộ đếm thời gian chơi trong mỗi ván chơi, nếu cùng một mức độ chơi thì người chơi ít thời gian hơn sẽ được đánh giá là tốt hơn.

Chúng ta sẽ dùng một JLable để lưu thời gian:

		point_lb = new JLabel("00:00:00:00");

Lable này sẽ được cập nhật liên tục bằng cách sử dụng Swing Timer trong Java.

		timer = new Timer(10, new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				point_lb.setText(next(point_lb));
			}
		});

Cứ 10 mili giây sẽ cập nhật một lần bằng cách thời đổi Text các Lable bằng hàm next().

Mình phải tạo ra hàm next() chính là thời gian tiếp theo sau 1 tích tắc nữa (1 giây = 100 mili giây).

	public String next(JLabel lb) {
		String str[] = lb.getText().split(":");
		int tt = Integer.parseInt(str[3]);
		int s = Integer.parseInt(str[2]);
		int m = Integer.parseInt(str[1]);
		int h = Integer.parseInt(str[0]);
		String kq = "";
		int sum = tt + s * 100 + m * 60 * 100 + h * 60 * 60 * 100 + 1;
		if (sum % 100 > 9)
			kq = ":" + sum % 100 + kq;
		else
			kq = ":0" + sum % 100 + kq;
		sum /= 100;
		
		if (sum % 60 > 9)
			kq = ":" + sum % 60 + kq;
		else
			kq = ":0" + sum % 60 + kq;
		sum /= 60;
		
		if (sum % 60 > 9)
			kq = ":" + sum % 60 + kq;
		else
			kq = ":0" + sum % 60 + kq;
		sum /= 60;
		if (sum > 9)
			kq = sum + kq;
		else
			kq = "0" + sum +kq;
		return kq;
	}

Chúng ta sẽ khởi động bộ đếm giờ này bằng  hàm start() khi ta click vào một ô vuông, khi người chơi thắng hoặc thua thì chúng ta sẽ dừng thời gian lại bằng hàm stop().

2. Số lượng cờ còn lại.

Trong trò chơi, người chơi phải cắm cờ lên tất cả những quả BOM thì mới có thể chiến thắng được, tuy vậy số lượng cờ lại có hạn, nếu có N quả BOM thì bạn chỉ được phép cắm N lá cờ ở ván chơi đó mà thôi.

Về vấn đề này mình đã sử dụng một JButton để lưu lại số cờ còn lại mà bạn có thể cắm.

		JButton mines_bt = new JButton(String.valueOf(BOM));

Ban đầu, ta khởi tạo Text của button đúng bằng số lương BOM, khi tiến hành cắm cờ thì lập tức giá trị con số trong Text của button sẽ giảm 1 đơn vị, ngược lại nếu gỡ cờ thì giá trị đó sẽ tắng 1 đơn vị, lưu ý trong lúc cắm cờ cần kiểm tra xem giá trị phần Text của button còn lớn hơn 0 hay là không.

Các hàm xử lý khi mở một ô vuông

1. Mở vào một ô không phải là BOM.

Phần này ta lại phải chia thành 2 phần:

1.1 Xung quanh ô vuông ấy có bom.

Nếu xung quanh một ô nào đấy có bom thì ta chỉ cần dùng hàm open() như bên dưới là xong:

	public void open(int i, int j) {
		if (tick[i][j] && values[i][j] != -1) {
			bt[i][j].setText(String.valueOf(values[i][j]));
			bt[i][j].setBackground(background_number_cl);
			tick[i][j] = false;
			checkWin();
		}
	}

Sau khi mở mỗi ô cần kiểm tra chiến thắng (Hàm này sẽ trình bày ở phía sau).

1.2 Xung quanh ô vuông ấy không bom.

Để xử lý được như trên chúng ta cần sử dụng thuật toán loang.

Nếu một ô vuông không mà xung quanh nó không có BOM, chúng ta lập tức (dùng hàm open() ở trên) mở hết 8 ô xung quanh nó, nếu xung quanh mỗi ô xung quanh nó cũng không có BOM, ta lại gọi đệ quy hàm loang đến các ô đó.

	public void openEmpty(int i, int j) {
		if (tick[i][j]) {
			tick[i][j] = false;
			bt[i][j].setBackground(background_null_cl);
			checkWin();
			for (int h = i - 1; h <= i + 1; h++)
				for (int k = j - 1; k <= j + 1; k++)
					if (h >= 0 && h <= m && k >= 0 && k <= n) {
						if (values[h][k] == 0 && tick[h][k])
							openEmpty(h, k);
						else
							open(h, k);
					}
		}
	}

2. Mở vào một ô có BOM

Khi mở vào một ô có BOM thì bạn lập tức thua ô màn chơi đó, chúng ta cần hiển thì vị trí của tất cả những quả BOM.

				if (values[i][j] == -1 ) {
					if (bt[i][j].getBackground() != flag_cl) {
						bt[i][j].setBackground(bom_cl);
						bt[i][j].setText("X");
						timer.stop();
						loss();
						JOptionPane.showMessageDialog(null, "Trúng Bom, bạn đã thua!");
						die = true;
					}
				}

Hàm loss() dùng để hàm thị tất cả vị trí của tất cả những quả BOM.

	public void loss() {
		for (int i = 1; i <= m; i++)
			for (int j = 1; j <= n; j++)
				if (values[i][j] == -1) {
					bt[i][j].setBackground(bom_cl);
					bt[i][j].setText("X");
				}
	}

Các phương thức xử lý cần thiết khác

1. Lấy điểm cao

Điểm cao (thời gian ngắn nhất hoàn thành màn chơi) sẽ được lưu ở một file point.txt theo từng dòng theo từng cấp độ.

	String hightPoint(int k) {
		String file = "point.txt";
		String str = "";
		FileReader fr;
		try {
			fr = new FileReader(file);
			BufferedReader br = new BufferedReader(fr);
			 try {
				 for (int i = 0; i <= k; i++)
					 str = br.readLine();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} catch (FileNotFoundException e) {
			String maxTime = "99:99:99:99";
			// TODO Auto-generated catch block
			FileWriter f;
			try {
				f = new FileWriter(file);
				f.write(maxTime + "\n");
				f.write(maxTime + "\n");
				f.write(maxTime + "\n");
				f.flush();
				f.close();
				return "99:99:99:99";
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
		}
		return str;
	}

2. Kiểm tra chiến thắng.

Người chơi chiến thắng khi đã mở tất cả các ô và bạn đã gán đúng tất cả các cờ vào đúng vị trí của các quả BOM.

	public void checkWin() {
		for (int i = 1; i <= m; i++)
			for (int j = 1; j <= n; j++)
				if (tick[i][j]) {
					return;
				}
		int k = 0;
		for (int i = 1; i <= m; i++)
			for (int j = 1; j <= n; j++)
				if (bt[i][j].getBackground() == flag_cl && values[i][j] != -1)
					return;
				else if (bt[i][j].getBackground() == flag_cl)
					k++;
		if (k <= BOM) {
			timer.stop();
			try {
				checkPoint(point_lb.getText(), lv.getSelectedIndex());
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			int s = 12;
			die = true;
			JOptionPane.showMessageDialog(null, "Bạn đã chiến thắng!");
		}
	}

3. Kiểm tra điểm cao

Sau khi chiến thắng thì chúng ta cần kiểm tra điểm cao, nếu thời gian chơi của mình tốt hơn thời gian tốt nhất hiện có thì chúng ta sẽ cập nhất thời gian tốt hơn mới vào flie.

	public void checkPoint(String s, int k) throws IOException {
		String file = "point.txt";
		FileReader fr = new FileReader(file);
		BufferedReader br = new BufferedReader(fr);
		String str[] = {"", "", ""};
		for (int i = 0; i <= 2; i++)
			str[i] = br.readLine();
		fr.close();
		if (s.compareTo(str[k]) < 0) {
			str[k] = s;
			FileWriter f = new FileWriter(file);
			f.write(str[0] + "\n");
			f.write(str[1] + "\n");
			f.write(str[2] + "\n");
			f.flush();
			f.close();
		}
	}

4. Hàm gắn - gỡ cờ.

Điều kiện đề gắn gỡ cờ là ô vuông đó chưa được mở.

	public void addFlag(int i, int j) {
		if (bt[i][j].getBackground() == flag_cl) {
				tick[i][j] = true;
				bt[i][j].setBackground(null);
				mines_bt.setText(String.valueOf(Integer.parseInt(mines_bt.getText()) + 1));
		}
		else if (tick[i][j] && Integer.parseInt(mines_bt.getText()) > 0) {
			tick[i][j] = false;
			bt[i][j].setBackground(flag_cl);
			mines_bt.setText(String.valueOf(Integer.parseInt(mines_bt.getText()) - 1));
		}
		checkWin();
	}

Các bạn có thể dùng một phím tắt để chuyển đổi giữa hai chế để gắn cờ và chế độ gỡ BOM.

	public void keyPressed(KeyEvent e) {
		if(e.getKeyCode() == key_flag) {
			flag = !flag;
		}
		if (flag) {
			flag_lb.setText("Đang cắm cờ (h)");
			flag_lb.setForeground(flag_cl);
		}
		else {
			flag_lb.setText("Đang gỡ bom (h)");
			flag_lb.setForeground(bom_cl);
		}
	}

Kết

Trên đây là tất cả những hàm xử lý và các điều cần thiết để tạo thàh trò chơi dò mìn hoàn chỉnh. Mọi ý kiến đóng góp xin góp ý ở phần bình luận.

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

Video demo: