我创建了一个Java Swing Minesweeper,我希望得到一些反馈。注:我只是在高中,所以请原谅任何‘愚蠢’的方式,我可能有编码的某些部分。
任何关于如何提高代码的一般质量的技巧或书籍,或者任何帮助我变得更好的信息,都会受到深深的赞赏。
除了反馈之外,我还想知道如何在我禁用的按钮中保留颜色。在这样的上下文中,为了使已经单击的单元格再次不可点击,我刚刚禁用了它。或者,是否有更好的方法来完成同样的任务?
守则的蓝图是:
请原谅语言上的歧义。
package MinesweeperGame;
//Following is the implementation of Minesweeper.
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.util.*;
public class Minesweeper extends JFrame implements ActionListener, MouseListener{
JFrame frame = new JFrame();
JButton reset = new JButton("Reset"); //Reset Button as a side.
JButton giveUp = new JButton("Give Up"); //Similarly, give up button.
JPanel ButtonPanel = new JPanel();
Container grid = new Container();
int[][] counts; //integer array to store counts of each cell. Used as a back-end for comparisons.
JButton[][] buttons; //Buttons array to use as a front end for the game.
int size,diff;
final int MINE = 10;
/**
@param size determines the size of the board
*/
public Minesweeper(int size){
super("Minesweeper");
this.size = size;
counts = new int[size][size];
buttons = new JButton[size][size];
frame.setSize(900,900);
frame.setLayout(new BorderLayout());
frame.add(ButtonPanel,BorderLayout.SOUTH);
reset.addActionListener(this);
giveUp.addActionListener(this);
grid.setLayout(new GridLayout(size,size));
for(int a = 0; a < buttons.length; a++)
{
for(int b = 0; b < buttons[0].length; b++)
{
buttons[a][b] = new JButton();
buttons[a][b].addActionListener(this);
grid.add(buttons[a][b]);
}
}
// above initializes each button in the minesweeper board and gives it functionality.
ButtonPanel.add(reset);
ButtonPanel.add(giveUp); // adding buttons to the panel.
frame.add(grid,BorderLayout.CENTER);
createMines(size); //calling function to start the game by filling mines.
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(EXIT_ON_CLOSE); //frame stuff
frame.setVisible(true);
}
/**
* Function to check whether user has lost the game ( i.e clicked a mine).
* @param m indicated whether the function has been called when user clicks a mine( m=1)
* or when he clicks the give up button.(m = any other integer).
* Shows a dialog box which tells the user that they have lost the game.
*/
public void takeTheL(int m){
for(int x = 0; x < size; x++)
{
for(int y = 0; y < size; y++)
{
if(buttons[x][y].isEnabled()) // when a button has been clicked, it is disabled.
{
if(counts[x][y] != MINE)
{
buttons[x][y].setText(""+ counts[x][y]);
}
else
{
buttons[x][y].setText("X");
}
buttons[x][y].setEnabled(false);
}
}
}
JOptionPane.showMessageDialog(null, m==1? "You clicked a mine!":"You Gave Up",
"Game Over", JOptionPane.ERROR_MESSAGE);
}
/**
* Function to check whether user has won or not
* It performs this by checking whether a cell that is NOT a mine
* remains to be clicked by the user.
* (Works because, when a user clicks a button, it is disabled to avoid further moves on the same cell).
* Function prints a pop-up message congratulating user on victory.
*/
public void takeTheW() {
boolean won = true;
for(int i = 0; i < size; i++)
{
for(int j = 0; j < size; j++)
{
if(counts[i][j] != MINE && buttons[i][j].isEnabled())
{
won = false;
}
}
}
if(won)
{
JOptionPane.showMessageDialog(null,"You have won!", "Congratulations!",
JOptionPane.INFORMATION_MESSAGE);
}
}
@Override
public void actionPerformed(ActionEvent ae) {
if(ae.getSource() == reset) //resets grid
{
for(int x = 0; x < size; x++)
{
for(int y = 0; y < size; y++)
{
buttons[x][y].setEnabled(true);
buttons[x][y].setText("");
}
}
createMines(30); //triggers a new game.
}
else if(ae.getSource() == giveUp) //user has given up. trigger takeTheL( m!= 1).
{
takeTheL(0); // anything not = 1
}
else // click was on a cell
{
for(int x = 0; x < size; x++)
{
for( int y = 0; y < size; y++)
{
if(ae.getSource() == buttons[x][y])
{
switch (counts[x][y]) {
case MINE:
buttons[x][y].setForeground(Color.RED);
buttons[x][y].setIcon(new ImageIcon("")); // add bomb image
takeTheL(1); //user clicked on a mine
break;
case 0:
buttons[x][y].setText(counts[x][y] +"");
buttons[x][y].setEnabled(false);
ArrayList<Integer> clear = new ArrayList<>();
clear.add(x*100+y);
dominoEffect(clear); // To recursively clear all surrounding '0' cells.
takeTheW(); //checks win every move
break;
default:
buttons[x][y].setText(""+counts[x][y]);
buttons[x][y].setEnabled(false);
takeTheW(); // its a number > 0 and not a mine, so just check for win
break;
}
}
}
}
}
}
/**
* Function creates mines at random positions.
* @param s the size of the board(row or column count)
*/
public void createMines(int s){
ArrayList<Integer> list = new ArrayList<>(); //Modifiable array to store pos. of mines.
for(int x = 0; x < s; x++)
{
for(int y = 0; y < s; y++)
{
list.add(x*100+y); // x & y shall be individually retrieved by dividing by 100 and modulo 100 respectively.
// refer to lines 284 and 285 for implementation
}
}
counts = new int[s][s]; //resetting back-end array
for(int a = 0; a < (int)(s * 1.5); a++)
{
int choice = (int)(Math.random() * list.size());
counts [list.get(choice) / 100] [list.get(choice) % 100] = MINE; //Using corollary of before-last comment to set mines as well.
list.remove(choice); // We don't want two mines in the same pos., so remove that pos. from list.
}
/*
Following segment initializes 'neighbor counts' for each cell. That is, the number of
mines that are present in the eight surrounding cells. IF the cell isn't a mine.
Note : It is done in the back-end array as that contains the numbers (MINE or 1 or 2 or 3 or 4 or 5 or 6 or 7 or 8)
*/
for(int x = 0; x < s; x++)
{
for(int y = 0; y < s; y++)
{
if(counts[x][y] != MINE)
{
int neighbor = 0;
if( x > 0 && y > 0 && counts[x-1][y-1] == MINE) //top left
{
neighbor++;
}
if( y > 0 && counts[x][y-1] == MINE) //left
{
neighbor++;
}
if( y < size - 1 && counts[x][y+1] == MINE) //right
{
neighbor++;
}
if( x < size - 1 && y > 0 && counts[x+1][y-1] == MINE) //bottom left
{
neighbor++;
}
if( x > 0 && counts[x-1][y] == MINE) //up
{
neighbor++;
}
if( x < size - 1 && counts[x+1][y] == MINE)//down
{
neighbor++;
}
if( x > 0 && y < size - 1 &&counts[x-1][y+1] == MINE) //top right
{
neighbor++;
}
if( x < size - 1 && y < size - 1 && counts[x+1][y+1] == MINE) //bottom right
{
neighbor++;
}
counts[x][y] = neighbor; //setting value
}
}
}
}
/**
* This function, called the domino effect, is an implementation of the idea that,
* when a cell with no surrounding mines is clicked, there's no point in user clicking
* all eight surrounding cells. Therefore, all surrounding
* cells' counts will be displayed in corresponding cells.
* The above is done recursively.
* @param toClear the ArrayList which is passed to the function with positions in array
* that are zero, and are subsequently clicked.
*/
public void dominoEffect(ArrayList<Integer> toClear){
if(toClear.isEmpty())
return; //base case
int x = toClear.get(0) / 100; //getting x pos.
int y = toClear.get(0) % 100; //getting y pos.
toClear.remove(0); //remove that element from array to prevent infinite recursion.
if(counts[x][y] == 0)
{ //similar to neighbor counts, each surrounding cell is filled
if( x > 0 && y > 0 && buttons[x-1][y-1].isEnabled()) //top left
{
buttons[x-1][y-1].setText(counts[x-1][y-1] + "");
buttons[x-1][y-1].setEnabled(false);
if(counts[x-1][y-1] == 0)
{
toClear.add((x-1)*100 + (y-1)); //to recursively implement, each surrounding cell is the new cell,
// the surrounding cells of which we shall check and so on.
}
}
if( y > 0 && buttons[x][y-1].isEnabled()) //left
{
buttons[x][y-1].setText(counts[x][y-1] + "");
buttons[x][y-1].setEnabled(false);
if(counts[x][y-1] == 0)
{
toClear.add(x*100 + (y-1));
}
}
if( y < size - 1 && buttons[x][y+1].isEnabled()) //right
{
buttons[x][y+1].setText(counts[x][y+1] + "");
buttons[x][y+1].setEnabled(false);
if(counts[x][y+1] == 0)
{
toClear.add(x*100 + (y+1));
}
}
if( x < size - 1 && y > 0 && buttons[x+1][y-1].isEnabled()) //bottom left
{
buttons[x+1][y-1].setText(counts[x+1][y-1] + "");
buttons[x+1][y-1].setEnabled(false);
if(counts[x+1][y-1] == 0)
{
toClear.add((x+1)*100 + (y-1));
}
}
if( x > 0 && buttons[x-1][y].isEnabled()) //up
{
buttons[x-1][y].setText(counts[x-1][y] + "");
buttons[x-1][y].setEnabled(false);
if(counts[x-1][y] == 0)
{
toClear.add((x-1)*100 + y);
}
}
if( x < size - 1 && buttons[x+1][y].isEnabled())//down
{
buttons[x+1][y].setText(counts[x+1][y] + "");
buttons[x+1][y].setEnabled(false);
if(counts[x+1][y] == 0)
{
toClear.add((x+1)*100 + y);
}
}
if( x > 0 && y < size - 1 && buttons[x-1][y+1].isEnabled()) //top right
{
buttons[x-1][y+1].setText(counts[x-1][y+1] + "");
buttons[x-1][y+1].setEnabled(false);
if(counts[x-1][y+1] == 0)
{
toClear.add((x-1)*100 + (y+1));
}
}
if( x < size - 1 && y < size - 1 && buttons[x+1][y+1].isEnabled()) //bottom right
{
buttons[x+1][y+1].setText(counts[x+1][y+1] + "");
buttons[x+1][y+1].setEnabled(false);
if(counts[x+1][y+1] == 0)
{
toClear.add((x+1)*100 + (y+1));
}
}
}
dominoEffect(toClear); //recursive call with list containing surrounding cells, for further check-and-clear of THEIR surr. cells.
}
//Main method.
public static void main(String[] args){
new Minesweeper(20); // Can be made of any size. (For now only squares)
}
@Override
public void mouseClicked(MouseEvent me) {
if (SwingUtilities.isRightMouseButton(me)){
// TODO : Handle flagging of mines.
}
}
@Override
public void mousePressed(MouseEvent me) {
// Do nothing
}
@Override
public void mouseReleased(MouseEvent me) {
// Do nothing
}
@Override
public void mouseEntered(MouseEvent me) {
// Do nothing
}
@Override
public void mouseExited(MouseEvent me) {
// Do nothing
}
}发布于 2019-03-11 15:49:58
你的回答让我想起了我在高中的时候,用秋千之类的东西写你写的东西。我决定重新过这段时间,并花了相当多的时间写一个全面的答案,并解释所有的重大决定。
上的改进
MouseListener,但从未实际使用它。我会把它完全移除。使用ActionListener足够。除非您添加了标记地雷的功能。这似乎是未来的一个补充。继续阅读,看到一个更优雅的解决方案。size而不是100来实现相同的事情(唯一的映射)。它还可以推广到网格的大小超出100的情况,在这种情况下,使用固定的常量将停止产生唯一的数字。dominoEffect方法的命名不是按照将方法命名为动词或动词短语的准则。cascade是我用的那个。另外,takeTheW和takeTheL可能还行,但我不喜欢它们。总是更喜欢描述方法正在做什么的名称。m中的参数takeTheL()。main方法。无可否认,我对这些评论略知一二,但我相信这段代码是可读的和不言自明的。如果您不理解某个特定片段,请留下评论。
package minesweeperimproved;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
import java.util.*;
/**
* This is the refactored version of the code presented in
* this post at CodeReview.SE:
* <p>
* https://codereview.stackexchange.com/questions/215081/created-a-minesweeper-game-on-java-using-swing-gui-i-wish-to-undertake-improvem
* <p>
* Original author: greyothello (https://codereview.stackexchange.com/users/194786/greyothello)
* Refactored by: HungryBlueDev (https://codereview.stackexchange.com/users/37479/hungry-blue-dev)
*/
public class Minesweeper {
// The value assigned to cells marked as mines. 10 works
// because no cell will have more than 8 neighbouring mines.
private static final int MINE = 10;
// The size in pixels for the frame.
private static final int SIZE = 500;
// The number of mines at generated is the grid size * this constant
private static final double POPULATION_CONSTANT = 1.5;
// This fixed amount of memory is to avoid repeatedly declaring
// new arrays every time a cell's neighbours are to be retrieved.
private static Cell[] reusableStorage = new Cell[8];
private int gridSize;
private Cell[][] cells;
private JFrame frame;
private JButton reset;
private JButton giveUp;
private final ActionListener actionListener = actionEvent -> {
Object source = actionEvent.getSource();
if (source == reset) {
createMines();
} else if (source == giveUp) {
revealBoardAndDisplay("You gave up.");
} else {
handleCell((Cell) source);
}
};
private class Cell extends JButton {
private final int row;
private final int col;
private int value;
Cell(final int row, final int col,
final ActionListener actionListener) {
this.row = row;
this.col = col;
addActionListener(actionListener);
setText("");
}
int getValue() {
return value;
}
void setValue(int value) {
this.value = value;
}
boolean isAMine() {
return value == MINE;
}
void reset() {
setValue(0);
setEnabled(true);
setText("");
}
void reveal() {
setEnabled(false);
setText(isAMine() ? "X" : String.valueOf(value));
}
void updateNeighbourCount() {
getNeighbours(reusableStorage);
for (Cell neighbour : reusableStorage) {
if (neighbour == null) {
break;
}
if (neighbour.isAMine()) {
value++;
}
}
}
void getNeighbours(final Cell[] container) {
// Empty all elements first
for (int i = 0; i < reusableStorage.length; i++) {
reusableStorage[i] = null;
}
int index = 0;
for (int rowOffset = -1; rowOffset <= 1; rowOffset++) {
for (int colOffset = -1; colOffset <= 1; colOffset++) {
// Make sure that we don't count ourselves
if (rowOffset == 0 && colOffset == 0) {
continue;
}
int rowValue = row + rowOffset;
int colValue = col + colOffset;
if (rowValue < 0 || rowValue >= gridSize
|| colValue < 0 || colValue >= gridSize) {
continue;
}
container[index++] = cells[rowValue][colValue];
}
}
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass())
return false;
Cell cell = (Cell) obj;
return row == cell.row &&
col == cell.col;
}
@Override
public int hashCode() {
return Objects.hash(row, col);
}
}
private Minesweeper(final int gridSize) {
this.gridSize = gridSize;
cells = new Cell[gridSize][gridSize];
frame = new JFrame("Minesweeper");
frame.setSize(SIZE, SIZE);
frame.setLayout(new BorderLayout());
initializeButtonPanel();
initializeGrid();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private void initializeButtonPanel() {
JPanel buttonPanel = new JPanel();
reset = new JButton("Reset");
giveUp = new JButton("Give Up");
reset.addActionListener(actionListener);
giveUp.addActionListener(actionListener);
buttonPanel.add(reset);
buttonPanel.add(giveUp);
frame.add(buttonPanel, BorderLayout.SOUTH);
}
private void initializeGrid() {
Container grid = new Container();
grid.setLayout(new GridLayout(gridSize, gridSize));
for (int row = 0; row < gridSize; row++) {
for (int col = 0; col < gridSize; col++) {
cells[row][col] = new Cell(row, col, actionListener);
grid.add(cells[row][col]);
}
}
createMines();
frame.add(grid, BorderLayout.CENTER);
}
private void resetAllCells() {
for (int row = 0; row < gridSize; row++) {
for (int col = 0; col < gridSize; col++) {
cells[row][col].reset();
}
}
}
private void createMines() {
resetAllCells();
final int mineCount = (int) POPULATION_CONSTANT * gridSize;
final Random random = new Random();
// Map all (row, col) pairs to unique integers
Set<Integer> positions = new HashSet<>(gridSize * gridSize);
for (int row = 0; row < gridSize; row++) {
for (int col = 0; col < gridSize; col++) {
positions.add(row * gridSize + col);
}
}
// Initialize mines
for (int index = 0; index < mineCount; index++) {
int choice = random.nextInt(positions.size());
int row = choice / gridSize;
int col = choice % gridSize;
cells[row][col].setValue(MINE);
positions.remove(choice);
}
// Initialize neighbour counts
for (int row = 0; row < gridSize; row++) {
for (int col = 0; col < gridSize; col++) {
if (!cells[row][col].isAMine()) {
cells[row][col].updateNeighbourCount();
}
}
}
}
private void handleCell(Cell cell) {
if (cell.isAMine()) {
cell.setForeground(Color.RED);
cell.reveal();
revealBoardAndDisplay("You clicked on a mine!");
return;
}
if (cell.getValue() == 0) {
Set<Cell> positions = new HashSet<>();
positions.add(cell);
cascade(positions);
} else {
cell.reveal();
}
checkForWin();
}
private void revealBoardAndDisplay(String message) {
for (int row = 0; row < gridSize; row++) {
for (int col = 0; col < gridSize; col++) {
if (!cells[row][col].isEnabled()) {
cells[row][col].reveal();
}
}
}
JOptionPane.showMessageDialog(
frame, message, "Game Over",
JOptionPane.ERROR_MESSAGE
);
createMines();
}
private void cascade(Set<Cell> positionsToClear) {
while (!positionsToClear.isEmpty()) {
// Set does not have a clean way for retrieving
// a single element. This is the best way I could think of.
Cell cell = positionsToClear.iterator().next();
positionsToClear.remove(cell);
cell.reveal();
cell.getNeighbours(reusableStorage);
for (Cell neighbour : reusableStorage) {
if (neighbour == null) {
break;
}
if (neighbour.getValue() == 0
&& neighbour.isEnabled()) {
positionsToClear.add(neighbour);
} else {
neighbour.reveal();
}
}
}
}
private void checkForWin() {
boolean won = true;
outer:
for (Cell[] cellRow : cells) {
for (Cell cell : cellRow) {
if (!cell.isAMine() && cell.isEnabled()) {
won = false;
break outer;
}
}
}
if (won) {
JOptionPane.showMessageDialog(
frame, "You have won!", "Congratulations",
JOptionPane.INFORMATION_MESSAGE
);
}
}
private static void run(final int gridSize) {
try {
// Totally optional. But this applies the look and
// feel for the current OS to the a application,
// making it look native.
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ignore) { }
// Launch the program
new Minesweeper(gridSize);
}
public static void main(String[] args) {
final int gridSize = 10;
SwingUtilities.invokeLater(() -> Minesweeper.run(gridSize));
}
}发布于 2019-03-11 07:26:47
第一步是研究MVC设计模式。现在,所有代码都在一个文件中。将游戏的逻辑分离成一个独立的“扫雷引擎”类,这个类不依赖于Swing框架。使引擎将事件发送到用户界面。
至于代码风格,请看一下这里的一些Java标记文章,了解如何格式化代码和声明变量(您没有遵循太多已知的良好实践)。
https://codereview.stackexchange.com/questions/215081
复制相似问题