我正在为Minesweeper创建一个简单的9x9网格。这个游戏的主要功能之一是,当点击的图块没有炸弹围绕它时,有一个递归来检查所有的边。在下面的代码中,我已经能够创建一个检查tile的上侧和左侧的函数。如果我添加更多方向,例如下方和右方,程序将崩溃并且无法正确显示切片。 (查看行countBorders
)
//MY MAIN PROBLEM
//显示主GUI 包Minesweeper4;
public class mainFrame {
public static void main(String[] args) {
new Grid().setVisible(true);
}
}
//主要代码
package Minesweeper4;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.*;
public class Grid extends JFrame implements ActionListener {
private JPanel mainGrid;
private JButton button1, button2;
private JButton[][] buttons = new JButton[9][9];
private String[][] mines = new String[9][9];
private ArrayList<ParentSquare> parentSquare = new ArrayList<ParentSquare>();
Random rand = new Random();
NumberSquare numberSquare = new NumberSquare();
MineSquare mineSquare = new MineSquare();
public void addMines() {
for (int j = 0; j < 9; j++) {
for (int k = 0; k < 9; k++) {
mines[j][k] = ".";
}
}
for (int i = 0; i < 3; i++) {
int temp_x = rand.nextInt(9);
int temp_y = rand.nextInt(9);
mines[temp_x][temp_y] = "x";
}
}
public void showMines() {
for (int x = 0; x < 9; x++) {
for (int y = 0; y < 9; y++) {
String temp = mines[x][y];
if (temp.equals("x")) {
System.out.println("X: " + (x + 1) + " Y: " + (y + 1) + " Value: " + temp);
}
}
}
}
public Grid() {
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(500, 500);
this.setTitle("Minesweeper 1.0");
mainGrid = new JPanel();
mainGrid.setLayout(new GridLayout(9, 9));
this.add(mainGrid);
button1 = new JButton("Boop");
button2 = new JButton("Poop");
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
buttons[i][j] = new JButton("");
buttons[i][j].addActionListener(this);
buttons[i][j].setBackground(Color.GRAY);
}
}
for (int k = 0; k < 9; k++) {
for (int l = 0; l < 9; l++) {
mainGrid.add(buttons[k][l]);
}
}
addMines();
showMines();
}
public void countBorders(int x, int y) {
int UL = 0, UU = 0, UR = 0, LL = 0, RR = 0, DL = 0, DD = 0, DR = 0, SUM = 0;
if (x > 0) {
UU = checkTile(x - 1, y);
}
if (y > 0) {
LL = checkTile(x, y - 1);
}
if (y < 8) {
RR = checkTile(x, y + 1);
}
if (x < 8) {
DD = checkTile(x + 1, y);
}
if ((x > 0) && (y > 0)) {
UL = checkTile(x - 1, y - 1);
}
if ((x > 0) && (y < 8)) {
UR = checkTile(x - 1, y + 1);
}
if ((x < 8) && (y > 0)) {
DL = checkTile(x + 1, y - 1);
}
if ((x < 8) && (y < 8)) {
DR = checkTile(x + 1, y + 1);
}
SUM = UL + UU + UR + LL + RR + DL + DD + DR;
printTile(x, y, SUM);
if (SUM == 0) { //MY MAIN PROBLEM
// if ((x > 0) && (y > 0)) {countBorders(x-1, y-1);} //Upper left
if (x > 0) {
countBorders(x - 1, y);
} //Upper
// if ((x > 0) && (y < 8)) {countBorders(x-1, y+1);} //Upper right
if (y > 0) {
countBorders(x, y - 1);
} //Left
// if (y < 8) {countBorders(x, y+1);} //Right
// if ((x < 8) && (y > 0)) {countBorders(x+1, y-1);} //Down Left
// if (x < 8) {countBorders(x+1, y);} //Down
// if ((x < 8) && (y < 8)) {countBorders(x+1, y+1);} //Down Right
}
}
public void printTile(int x, int y, int SUM) {
String text = Integer.toString(SUM);
buttons[x][y].setText(text);
buttons[x][y].setBackground(Color.CYAN);
}
public int checkTile(int x, int y) {
String c = mines[x][y];
if (c.equals("x")) {
return 1;
} else {
return 0;
}
}
public void click(int x, int y) {
String mine = mines[x][y];
if (mine.equals("x")) {
System.out.println("Bomb!!!");
buttons[x][y].setText("!");
buttons[x][y].setBackground(Color.RED);
} else {
countBorders(x, y);
System.out.println("Safe!!!");
// buttons[x][y].setText("√");
// buttons[x][y].setBackground(Color.WHITE);
}
}
@Override
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (e.getSource() == buttons[i][j]) {
System.out.println("Clicked Tile X: " + (i + 1) + " Y: " + (j + 1));
//buttons[i][j].setText("!");
click(i, j);
}
}
}
}
}
有没有办法解决这个递归问题? 提前谢谢你,我真的想学习Java。祝你有愉快的一天!
答案 0 :(得分:4)
您的错误导致的递归没有我能找到的停止逻辑,您需要做的是以某种方式检查以确保在重新计数之前尚未计算或按下某个单元格。否则代码可能会引发stackoverflow错误。这将需要给细胞计数一些状态,告诉你这些信息,这将告诉你细胞是否已被计数。
有关执行此逻辑的成功程序的示例,请随意查看我的Swing GUI示例,这是我5年前创建的示例。在这段代码中,我有一个类MineCellModel,为单个扫雷单元提供逻辑(不是GUI),并且该类包含一个布尔字段,按下,这是假的,直到通过用户按下等效按钮或者在模型的逻辑中递归地“按下”单元格。如果按下单元格,如果布尔值为true,则递归停止在此单元格中。
您可以在此处找到代码:Minesweeper Action Events。这是一个旧程序,所以我为任何可能关闭的概念或代码道歉。
运行代码会产生以下结果:
以下是单个文件中的代码:
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.SwingConstants;
import javax.swing.event.SwingPropertyChangeSupport;
@SuppressWarnings("serial")
public class MineSweeper {
private JPanel mainPanel = new JPanel();
private MineCellGrid mineCellGrid;
private JButton resetButton = new JButton("Reset");
public MineSweeper(int rows, int cols, int mineTotal) {
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS));
mineCellGrid = new MineCellGrid(rows, cols, mineTotal);
resetButton.setMnemonic(KeyEvent.VK_R);
resetButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
mineCellGrid.reset();
}
});
mainPanel.add(mineCellGrid);
mainPanel.add(new JSeparator());
mainPanel.add(new JPanel() {
{
add(resetButton);
}
});
}
private JPanel getMainPanel() {
return mainPanel;
}
private static void createAndShowUI() {
JFrame frame = new JFrame("MineSweeper");
// frame.getContentPane().add(new MineSweeper(20, 20,
// 44).getMainPanel());
frame.getContentPane().add(new MineSweeper(12, 12, 13).getMainPanel());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowUI();
}
});
}
}
@SuppressWarnings("serial")
class MineCellGrid extends JPanel {
private MineCellGridModel model;
private List<MineCell> mineCells = new ArrayList<MineCell>();
public MineCellGrid(final int maxRows, final int maxCols, int mineNumber) {
model = new MineCellGridModel(maxRows, maxCols, mineNumber);
setLayout(new GridLayout(maxRows, maxCols));
for (int row = 0; row < maxRows; row++) {
for (int col = 0; col < maxCols; col++) {
MineCell mineCell = new MineCell(row, col);
add(mineCell);
mineCells.add(mineCell);
model.add(mineCell.getModel(), row, col);
}
}
reset();
}
public void reset() {
model.reset();
for (MineCell mineCell : mineCells) {
mineCell.reset();
}
}
}
class MineCellGridModel {
private MineCellModel[][] cellModelGrid;
private List<Boolean> mineList = new ArrayList<Boolean>();
private CellModelPropertyChangeListener cellModelPropChangeListener = new CellModelPropertyChangeListener();
private int maxRows;
private int maxCols;
private int mineNumber;
private int buttonsRemaining;
public MineCellGridModel(final int maxRows, final int maxCols, int mineNumber) {
this.maxRows = maxRows;
this.maxCols = maxCols;
this.mineNumber = mineNumber;
for (int i = 0; i < maxRows * maxCols; i++) {
mineList.add((i < mineNumber) ? true : false);
}
cellModelGrid = new MineCellModel[maxRows][maxCols];
buttonsRemaining = (maxRows * maxCols) - mineNumber;
}
public void add(MineCellModel model, int row, int col) {
cellModelGrid[row][col] = model;
model.addPropertyChangeListener(cellModelPropChangeListener);
}
public void reset() {
buttonsRemaining = (maxRows * maxCols) - mineNumber;
// randomize the mine location
Collections.shuffle(mineList);
// reset the model grid and set mines
for (int r = 0; r < cellModelGrid.length; r++) {
for (int c = 0; c < cellModelGrid[r].length; c++) {
cellModelGrid[r][c].reset();
cellModelGrid[r][c].setMined(mineList.get(r * cellModelGrid[r].length + c));
}
}
// advance value property of all neighbors of a mined cell
for (int r = 0; r < cellModelGrid.length; r++) {
for (int c = 0; c < cellModelGrid[r].length; c++) {
if (cellModelGrid[r][c].isMined()) {
int rMin = Math.max(r - 1, 0);
int cMin = Math.max(c - 1, 0);
int rMax = Math.min(r + 1, cellModelGrid.length - 1);
int cMax = Math.min(c + 1, cellModelGrid[r].length - 1);
for (int row2 = rMin; row2 <= rMax; row2++) {
for (int col2 = cMin; col2 <= cMax; col2++) {
cellModelGrid[row2][col2].incrementValue();
}
}
}
}
}
}
private class CellModelPropertyChangeListener implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent evt) {
MineCellModel model = (MineCellModel) evt.getSource();
int row = model.getRow();
int col = model.getCol();
if (evt.getPropertyName().equals(MineCellModel.BUTTON_PRESSED)) {
if (cellModelGrid[row][col].isMineBlown()) {
mineBlown();
} else {
buttonsRemaining--;
if (buttonsRemaining <= 0) {
JOptionPane.showMessageDialog(null, "You've Won!!!", "Congratulations",
JOptionPane.PLAIN_MESSAGE);
}
if (cellModelGrid[row][col].getValue() == 0) {
zeroValuePress(row, col);
}
}
}
}
private void mineBlown() {
for (int r = 0; r < cellModelGrid.length; r++) {
for (int c = 0; c < cellModelGrid[r].length; c++) {
MineCellModel model = cellModelGrid[r][c];
if (model.isMined()) {
model.setMineBlown(true);
}
}
}
}
private void zeroValuePress(int row, int col) {
int rMin = Math.max(row - 1, 0);
int cMin = Math.max(col - 1, 0);
int rMax = Math.min(row + 1, cellModelGrid.length - 1);
int cMax = Math.min(col + 1, cellModelGrid[row].length - 1);
for (int row2 = rMin; row2 <= rMax; row2++) {
for (int col2 = cMin; col2 <= cMax; col2++) {
cellModelGrid[row2][col2].pressedAction();
}
}
}
}
}
@SuppressWarnings("serial")
class MineCell extends JPanel {
private static final String LABEL = "label";
private static final String BUTTON = "button";
private static final int PS_WIDTH = 24;
private static final int PS_HEIGHT = PS_WIDTH;
private static final float LABEL_FONT_SIZE = (float) (24 * PS_WIDTH) / 30f;
private static final float BUTTON_FONT_SIZE = (float) (14 * PS_WIDTH) / 30f;
private JButton button = new JButton();
private JLabel label = new JLabel(" ", SwingConstants.CENTER);
private CardLayout cardLayout = new CardLayout();
private MineCellModel model;
public MineCell(final boolean mined, int row, int col) {
model = new MineCellModel(mined, row, col);
model.addPropertyChangeListener(new MyPCListener());
label.setFont(label.getFont().deriveFont(Font.BOLD, LABEL_FONT_SIZE));
button.setFont(button.getFont().deriveFont(Font.PLAIN, BUTTON_FONT_SIZE));
button.setMargin(new Insets(1, 1, 1, 1));
setLayout(cardLayout);
add(button, BUTTON);
add(label, LABEL);
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
pressedAction();
}
});
button.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON3) {
model.upDateButtonFlag();
}
}
});
}
public MineCell(int row, int col) {
this(false, row, col);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(PS_WIDTH, PS_HEIGHT);
}
public void pressedAction() {
if (model.isFlagged()) {
return;
}
model.pressedAction();
}
public void showCard(String cardConstant) {
cardLayout.show(this, cardConstant);
}
// TODO: have this change the button's icon
public void setFlag(boolean flag) {
if (flag) {
button.setBackground(Color.yellow);
button.setForeground(Color.red);
button.setText("f");
} else {
button.setBackground(null);
button.setForeground(null);
button.setText("");
}
}
private void setMineBlown(boolean mineBlown) {
if (mineBlown) {
label.setBackground(Color.red);
label.setOpaque(true);
showCard(LABEL);
} else {
label.setBackground(null);
}
}
public MineCellModel getModel() {
return model;
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
model.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
model.removePropertyChangeListener(listener);
}
private class MyPCListener implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent evt) {
String propName = evt.getPropertyName();
if (propName.equals(MineCellModel.MINE_BLOWN)) {
setMineBlown(true);
} else if (propName.equals(MineCellModel.FLAG_CHANGE)) {
setFlag(model.isFlagged());
} else if (propName.equals(MineCellModel.BUTTON_PRESSED)) {
if (model.isMineBlown()) {
setMineBlown(true);
} else {
String labelText = (model.getValue() == 0) ? "" : String.valueOf(model
.getValue());
label.setText(labelText);
}
showCard(LABEL);
}
}
}
public void reset() {
setFlag(false);
setMineBlown(false);
showCard(BUTTON);
label.setText("");
}
}
class MineCellModel {
public static final String FLAG_CHANGE = "Flag Change";
public static final String BUTTON_PRESSED = "Button Pressed";
public static final String MINE_BLOWN = "Mine Blown";
private int row;
private int col;
private int value = 0;
private boolean mined = false;;
private boolean flagged = false;
private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
private boolean pressed = false;
private boolean mineBlown = false;
public MineCellModel(boolean mined, int row, int col) {
this.mined = mined;
this.row = row;
this.col = col;
}
public void incrementValue() {
int temp = value + 1;
setValue(temp);
}
public void setValue(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void setMineBlown(boolean mineBlown) {
this.mineBlown = mineBlown;
PropertyChangeEvent evt = new PropertyChangeEvent(this, MINE_BLOWN, false, true);
pcSupport.firePropertyChange(evt);
}
public boolean isMineBlown() {
return mineBlown;
}
public void setMined(boolean mined) {
this.mined = mined;
}
public void setFlagged(boolean flagged) {
this.flagged = flagged;
}
public int getRow() {
return row;
}
public int getCol() {
return col;
}
public boolean isMined() {
return mined;
}
public boolean isFlagged() {
return flagged;
}
public void pressedAction() {
if (pressed) {
return;
}
pressed = true;
if (mined) {
setMineBlown(true);
}
PropertyChangeEvent evt = new PropertyChangeEvent(this, BUTTON_PRESSED, -1, value);
pcSupport.firePropertyChange(evt);
}
public void upDateButtonFlag() {
boolean oldValue = flagged;
setFlagged(!flagged);
PropertyChangeEvent evt = new PropertyChangeEvent(this, FLAG_CHANGE, oldValue, flagged);
pcSupport.firePropertyChange(evt);
}
public void reset() {
mined = false;
flagged = false;
pressed = false;
mineBlown = false;
value = 0;
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcSupport.removePropertyChangeListener(listener);
}
}
我的代码使用递归,但具有间接级别,因为它基于模型 - 视图 - 控制器类型的设计模式,并且递归在侦听器的通知内。请注意,每个GUI MineCell对象都拥有自己的MineCellModel对象,后者保存MineCell的状态。当按下MineCell对象中保存的GUI JButton时,其ActionListener将调用同一个类的pressed()
方法:
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
pressedAction();
}
});
如果名为flagged的布尔值为true
,则此方法首先检查相应的MineCellModel以查看它是否已被“标记”。如果是这样,这意味着用户右键单击了该按钮,并且它未处于活动状态,因此该方法返回。否则调用MineCellModel的pressedAction()
方法,
public void pressedAction() {
if (model.isFlagged()) {
return;
}
model.pressedAction();
}
这里是递归开始的地方,它通过观察者设计模式来实现:
// within MineCellModel
public void pressedAction() {
if (pressed) {
// if the button's already been pressed -- return, do nothing
return;
}
// otherwise make pressed true
pressed = true;
// if we've hit a mine -- blow it!
if (mined) {
setMineBlown(true);
}
// *** Here's the key *** notify all listeners that this button has been pressed
PropertyChangeEvent evt = new PropertyChangeEvent(this, BUTTON_PRESSED, -1, value);
pcSupport.firePropertyChange(evt);
}
底部的两行代码通知此模型的任何侦听器其BUTTON_PRESSED状态已更改,并将MineCellModel的value
发送给所有侦听器。 int值是关键字,因为它具有地雷的邻居数量。什么听MineCellModel?好吧,一个关键对象是MineCellGridModel,这个模型代表整个网格的状态。它有一个CellModelPropertyChangeListener
类来完成实际的监听,在这个类中有以下代码:
private class CellModelPropertyChangeListener implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent evt) {
// first get the MineCellModel for the cell that triggered this notification
MineCellModel model = (MineCellModel) evt.getSource();
int row = model.getRow();
int col = model.getCol();
// if the event is a button pressed event
if (evt.getPropertyName().equals(MineCellModel.BUTTON_PRESSED)) {
// first check if a mine was hit, and if so, call mineBlown()
if (cellModelGrid[row][col].isMineBlown()) {
mineBlown(); // this method iterates through all cells and blows all mines
} else {
// here we check for a winner
buttonsRemaining--;
if (buttonsRemaining <= 0) {
JOptionPane.showMessageDialog(null, "You've Won!!!", "Congratulations",
JOptionPane.PLAIN_MESSAGE);
}
// here is the key spot -- if cell's value is 0, call the zeroValuePress method
if (cellModelGrid[row][col].getValue() == 0) {
zeroValuePress(row, col);
}
}
}
}
private void mineBlown() {
// ... code to blow all the un-blown mines
}
// this code is called if a button pressed has 0 value -- no mine neighbors
private void zeroValuePress(int row, int col) {
// find the boundaries of the neighbors
int rMin = Math.max(row - 1, 0); // check for the top edge
int cMin = Math.max(col - 1, 0); // check for the left edge
int rMax = Math.min(row + 1, cellModelGrid.length - 1); // check for the bottom edge
int cMax = Math.min(col + 1, cellModelGrid[row].length - 1); // check for right edge
// iterate through the neighbors
for (int row2 = rMin; row2 <= rMax; row2++) {
for (int col2 = cMin; col2 <= cMax; col2++) {
// *** Here's the recursion ***
// call pressedAction on all the neighbors
cellModelGrid[row2][col2].pressedAction();
}
}
}
}
因此上面监听器中的关键方法是zeroValuePress(...)
方法。它首先找到当前矿区周围邻居的边界,使用Math.min(...)
和Math.max(...)
注意不要超出网格的右边,左边或顶部或底部边界。然后,它遍历在此网格所持有的每个邻居MineCellModel上调用pressedAction()
的单元邻居。如您所知,pressedAction()
方法将检查单元格是否已被按下,如果没有,则更改其状态,然后通知此同一个侦听器,从而导致递归。
答案 1 :(得分:2)
这个游戏的主要功能之一是,当点击的图块没有围绕它的炸弹时,有一个递归来检查所有边。
看起来你被困在你需要根据它周围的炸弹数量更新数字的部分。
这些是您需要注意的事项:
要更新单元格上的数字, 无需 可以使用递归。我使用递归的唯一部分是当用户点击带有value == 0
的单元格时(踩空网格)。
无需编写大量if-conditions
即可轻松完成所有8个方向的检查。您只需要一对嵌套的for-loop
。只需像二维数组一样遍历3x3
网格(参见下图)。
如图所示遍历3x3
矩阵,我们可以使用一对嵌套循环:
for(int x=(coordX-1); x<=(coordX+1); x++)
for(int y=(coordY-1); y<=(coordY+1); y++)
if(x!=-1 && y!= -1 && x! = ROWS && y! = COLS && map[x][y] != 'B')
if(map[x][y] == '.')
map[x][y] = '1';
else
map[x][y] += 1;
if-condition
阻止处理超出范围的数组元素。