我是一个初学者程序员,试图自学如何编写代码。为了提高我的技能,我目前正在进行一个项目,以创建一个windows扫雷器的精确副本。它是用JavaFX编写的,大部分的基本代码都是完成的。我可以运行程序,以发挥扫雷没有错误(也许)。
如果有人想查看我的代码(尽管我对代码大小非常怀疑),我将非常感激。
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Game extends Application {
private NumberDisplay mineCount = new NumberDisplay(DIGITS);
private int numMine;
private TimeDisplay time = new TimeDisplay(DIGITS);
private Board board;
private MainGame mainGame;
public static void main(String[] a){
launch(a);
}
public void start(Stage stage) throws Exception {
board = new Board(9, 9);
mainGame = new MainGame(board, Difficulty.EASY);
updateMineCount();
HBox numberLayout = new HBox(10);
VBox mainLayout = new VBox(10);
numberLayout.getChildren().addAll(time, mineCount);
mainLayout.getChildren().addAll(numberLayout, mainGame);
Scene scene = new Scene(mainLayout);
stage.setScene(scene);
time.start();
stage.show();
mainGame.setOnMouseClicked(e -> {
if (mainGame.isEnd()){
time.stop();
if (mainGame.isWin()){
win();
} else {
lose();
}
} else {
if (e.getButton().equals(MouseButton.SECONDARY)){
updateMineCount();
}
}
});
}
private void updateMineCount(){
numMine = board.getNumMine() - Cell.getNumFlag();
mineCount.setNumber(numMine);
mineCount.update();
}
private void win(){
System.out.println("win");
}
private void lose(){
System.out.println("lose" + time.getTime());
}
private static final int DIGITS = 3;
}import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.GridPane;
public class MainGame extends GridPane{
private ImageView[][] cell;
private boolean win;
private boolean end;
public MainGame(Board board, Difficulty difficulty){
board.init(difficulty);
cell = new ImageView[board.getYSize()][board.getXSize()];
for (int i = 0; i < board.getYSize(); i++){
for (int j = 0; j < board.getXSize(); j++){
cell[i][j] = new ImageView(board.getCell(j, i).getUnselectedImage());
cell[i][j].setFitHeight(CELL_SIZE);
cell[i][j].setFitWidth(CELL_SIZE);
GridPane.setRowIndex(cell[i][j], i + 1);
GridPane.setColumnIndex(cell[i][j], j + 1);
this.getChildren().add(cell[i][j]);
}
}
assignEvent(board);
}
private void assignEvent(Board board){
for (ImageView[] cellRow: this.getCell()){
for (ImageView cell: cellRow){
cell.setOnMouseClicked(e -> {
int[] index = getClickedIndex(cell, board);
int x = index[0];
int y = index[1];
if (e.getButton().equals(MouseButton.SECONDARY)){
if (!(board.getCell(x, y).isSelected())){
flag(x, y, board);
}
} else {
if (!(board.getCell(x,y).isFlagged())){
selectCell(x, y, x, y, board);
if (board.getBoardSize() - board.getNumMine() == Cell.getNumSelectedCell()){
win();
}
}
}
});
}
}
}
private void flag(int x, int y, Board board){
board.getCell(x, y).flag();
if (board.getCell(x, y).isFlagged()){
cell[y][x].setImage(board.getCell(x, y).getFlagImage());
} else {
cell[y][x].setImage(board.getCell(x, y).getUnselectedImage());
}
}
private void selectCell(int firstX, int firstY, int x, int y, Board board){
this.cell[y][x].setImage(board.getCell(x, y).getSelectedImage());
board.getCell(x, y).select();
if (board.getCell(x,y).getID().equals(CellValue.MINE) && x == firstX && y == firstY){
lose(board);
} else if (board.getCell(x,y).getMineCount() == 0){
selectSurroundingCell(firstX, firstY, x, y, board);
}
}
private void selectSurroundingCell(int firstX, int firstY, int x, int y, Board board){
for (int i = (y - 1); i <= (y + 1); i++){
for (int j = (x - 1); j <= (x + 1); j++){
try {
if (board.getCell(j, i).isSelected()){
continue;
}
if (i == y && j == x){
continue;
}
selectCell(firstX, firstY, j, i, board);
} catch (IndexOutOfBoundsException ex){
continue;
}
}
}
}
private int[] getClickedIndex(ImageView cell, Board board){
int[] index = new int[2];
for (int i = 0; i < board.getYSize(); i++){
for (int j = 0; j < board.getXSize(); j++){
if (cell.equals(this.cell[i][j])){
index[0] = j;
index[1] = i;
}
}
}
return index;
}
private void win(){
end = true;
win = true;
}
private void lose(Board board){
displayAll(board);
end = true;
win = false;
}
public boolean isWin(){
return win;
}
public boolean isEnd(){
return end;
}
private void displayAll(Board board){
for (int i = 0; i < board.getYSize(); i++){
for (int j = 0; j < board.getXSize(); j++){
if (!(board.getCell(j, i).isSelected())){
this.cell[i][j].setImage(board.getCell(j, i).getSelectedImage());
}
}
}
}
public ImageView getCell(int x, int y){
return cell[y][x];
}
public ImageView[][] getCell(){
return cell;
}
public static final int CELL_SIZE = 20;
}import java.util.Random;
public class Board {
private Cell[][] cells;
private Random random = new Random();
private int numMine;
public Board(int xSize, int ySize){
cells = new Cell[xSize][ySize];
}
public void init(Difficulty difficulty){
initEmptyCell();
numMine = initNumMine(difficulty);
initMine();
initMineCount();
}
public void init(int numMine) throws TooMuchMineException{
if (numMine >= ((cells.length - 1) * (cells[0].length - 1))){
throw new TooMuchMineException();
}
initEmptyCell();
this.numMine = numMine;
initMine();
initMineCount();
}
private void initEmptyCell(){
for (int i = 0; i < cells.length; i++){
for (int j = 0; j < cells[0].length; j++){
cells[i][j] = new Cell();
}
}
}
private int initNumMine(Difficulty difficulty){
switch(difficulty){
case EASY: return getBoardSize() / EASY_FACTOR;
case MEDIUM: return getBoardSize() / MEDIUM_FACTOR;
case HARD: return getBoardSize() / HARD_FACTOR;
default: return 0;
}
}
private void initMine(){
for (int i = 0; i < numMine; i++){
while(true){
Cell randomCell = cells[random.nextInt(cells.length)][random.nextInt(cells[0].length)];
if (!(randomCell.getID().equals(CellValue.MINE))){
randomCell.setMine();
break;
}
}
}
}
private void initMineCount(){
for (int i = 0; i < cells.length; i++){
for (int j = 0; j < cells[0].length; j++){
if (cells[i][j].getID().equals(CellValue.MINE)){
continue;
}
int mineCount = 0;
mineCount = getMineCount(j, i);
cells[i][j].setMineCount(mineCount);
}
}
}
public Cell getCell(int x, int y){
return cells[y][x];
}
public Cell[][] getCell(){
return cells;
}
private int getMineCount(int x, int y){
int mineCount = 0;
for (int i = (y - 1); i <= (y + 1); i++){
for (int j = (x - 1); j <= (x + 1); j++){
if (i == y && j == x) continue;
try {
if (cells[i][j].getID().equals(CellValue.MINE)){
mineCount++;
}
} catch (IndexOutOfBoundsException ex){
continue;
}
}
}
return mineCount;
}
public int getBoardSize(){
return getYSize() * this.getXSize();
}
public int getXSize(){
return cells[0].length;
}
public int getYSize(){
return cells.length;
}
public int getNumMine(){
return numMine;
}
private static final int EASY_FACTOR = 8;
private static final int MEDIUM_FACTOR = 6;
private static final int HARD_FACTOR = 4;
}import javafx.scene.image.Image;
public class Cell {
private CellValue id;
private int mineCount;
private boolean isSelected = false;
private boolean isFlagged = false;
private static int numFlag = 0;
private static int numSelectedCell = 0;
public Cell(){
this(CellValue.EMPTY);
}
public Cell(CellValue id){
this.id = id;
}
public void setMine(){
id = CellValue.MINE;
}
public CellValue getID(){
return id;
}
public void select(){
isSelected = true;
if (isFlagged){
flag();
}
numSelectedCell++;
}
public void flag(){
isFlagged = !isFlagged;
if (this.isFlagged()){
numFlag++;
} else {
numFlag--;
}
}
public boolean isSelected(){
return isSelected;
}
public boolean isFlagged(){
return isFlagged;
}
public String toString(){
switch(id){
case MINE: return (String.format("mine, %d", mineCount));
default: return (String.format("empty, %d", mineCount));
}
}
public void setMineCount(int mineCount){
this.mineCount = mineCount;
}
public int getMineCount(){
return mineCount;
}
public Image getUnselectedImage(){
return unselected;
}
public Image getFlagImage(){
return flag;
}
public Image getSelectedImage(){
if (id.equals(CellValue.MINE)){
return getMineImage();
}
switch (mineCount){
case 0: return zero;
case 1: return one;
case 2: return two;
case 3: return three;
case 4: return four;
case 5: return five;
case 6: return six;
case 7: return seven;
case 8: return eight;
default : return null;
}
}
public Image getMineImage(){
return mine;
}
public static int getNumSelectedCell(){
return numSelectedCell;
}
public static int getNumFlag(){
return numFlag;
}
private static Image unselected = new Image("image/unselected.png");
private static Image mine = new Image("image/mine.png");
private static Image flag = new Image("image/flag.png");
private static Image zero = new Image("image/zero.png");
private static Image one = new Image("image/one.png");
private static Image two = new Image("image/two.png");
private static Image three = new Image("image/three.png");
private static Image four = new Image("image/four.png");
private static Image five = new Image("image/five.png");
private static Image six = new Image("image/six.png");
private static Image seven = new Image("image/seven.png");
private static Image eight = new Image("image/eight.png");
}public enum CellValue {
EMPTY, MINE;
}public enum Difficulty {
EASY, MEDIUM, HARD;
}import java.util.ArrayList;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
public class NumberDisplay extends HBox {
private int number;
private ArrayList<ImageView> ivs;
public NumberDisplay(){
this(0);
}
public NumberDisplay(int digit){
super();
number = 0;
ivs = new ArrayList<ImageView>();
for (int i = 0; i < digit; i++){
ivs.add(new ImageView());
ivs.get(i).setFitHeight(DISPLAY_HEIGHT);
ivs.get(i).setFitWidth(DISPLAY_WIDTH);
this.getChildren().add(ivs.get(i));
}
this.update();
}
public void setNumber(int number){
this.number = number;
}
public int getNumber(){
return number;
}
public void update(){
int[] digits = parseNumber();
if (number < 0){
for (int i = 0; i < ivs.size() - 1; i++){
setImage(ivs.get(ivs.size() - 1 - i), digits[i]);
}
ivs.get(0).setImage(negative);
} else {
for (int i = 0; i < ivs.size(); i++){
setImage(ivs.get(ivs.size() - 1 - i), digits[i]);
}
}
}
private int[] parseNumber(){
int[] digits = new int[ivs.size()];
if (number >= Math.pow(10, ivs.size())){
for (int i = 0; i < digits.length; i++){
digits[i] = 9;
}
} else if (number <= -Math.pow(10, ivs.size() - 1)) {
for (int i = 0; i < digits.length; i++){
digits[i] = 9;
}
} else {
for (int i = 0; i < digits.length; i++){
digits[i] = (number % ((int)(Math.pow(10, i + 1)))) / (int)(Math.pow(10, i));
}
}
return digits;
}
private void setImage(ImageView iv, int digit){
switch (digit){
case 1: case -1: iv.setImage(one); break;
case 2: case -2: iv.setImage(two); break;
case 3: case -3: iv.setImage(three); break;
case 4: case -4: iv.setImage(four); break;
case 5: case -5: iv.setImage(five); break;
case 6: case -6: iv.setImage(six); break;
case 7: case -7: iv.setImage(seven); break;
case 8: case -8: iv.setImage(eight); break;
case 9: case -9: iv.setImage(nine); break;
case 0: iv.setImage(zero); break;
default: iv.setImage(zero); break;
}
}
private static Image zero = new Image("image/digitalnumber/zero.png");
private static Image one = new Image("image/digitalnumber/one.png");
private static Image two = new Image("image/digitalnumber/two.png");
private static Image three = new Image("image/digitalnumber/three.png");
private static Image four = new Image("image/digitalnumber/four.png");
private static Image five = new Image("image/digitalnumber/five.png");
private static Image six = new Image("image/digitalnumber/six.png");
private static Image seven = new Image("image/digitalnumber/seven.png");
private static Image eight = new Image("image/digitalnumber/eight.png");
private static Image nine = new Image("image/digitalnumber/nine.png");
private static Image negative = new Image("image/digitalnumber/negative.png");
private static final int DISPLAY_HEIGHT = 30;
private static final int DISPLAY_WIDTH = 20;
}import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.util.Duration;
public class TimeDisplay extends NumberDisplay {
Timeline timeline;
public TimeDisplay(int digit){
super(digit);
this.update();
}
public void start(){
timeline = new Timeline(new KeyFrame(
Duration.millis(1000),
ae -> addSecond()));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
}
public void stop(){
timeline.stop();
}
public void reset(){
this.setNumber(0);
}
public int getTime(){
return this.getNumber();
}
private void addSecond(){
this.setNumber(this.getNumber() + 1);
this.update();
}
}public class TooMuchMineException extends RuntimeException {
public TooMuchMineException(){
super();
}
public TooMuchMineException(String msg){
super(msg);
}
public TooMuchMineException(String msg, Throwable cause){
super(msg, cause);
}
public TooMuchMineException(Throwable cause){
super(cause);
}
}发布于 2015-07-12 14:07:51
我从C# winforms程序学到的一个技巧是让入口点尽可能少地让程序启动和运行。我认为这应该是所有高层次概念课程的核心基础。Main也不例外,事实上,它处于程序的最高级别,因此应该只做最小的事情来启动您的程序。我想到的一个例子应该是这样的。
@Override
public void start(Stage stage) throws Exception{
MainGame game = GameFactory.create(new Board(9,9), Difficulty.EASY);
Scene scene = new Scene(GameWindowLayoutFactory.create(game));
stage.setScene(scene);
stage.show();
}上面的代码只需很少的脑力就可以知道它所做的事情,这对于您的代码的最高级别来说是完美的。如果一个新的人进来并想用UI修复一些东西,那么他们就会开始钻入GameWindowLayoutFactory。如果这是游戏逻辑,那么他们就开始钻入GameFactory。
这使我想到我早些时候向您建议的关于研究设计模式的评论。他们容易理解,但需要一些实践和理解,以了解何时和如何有效地使用它们。工厂模式(IMO)很容易被过度使用,从而使您的代码更难理解。这就是说,我个人将限制工厂主要用于高级代码,或者当您希望对代码有更流畅的感觉时。现在,即使您决定在这个场景中不使用工厂,我还是建议使用更多的方法(对它们有好的名称),以便仍然可以轻松地阅读。我已经封装了您的start方法,使它现在看起来像这样(以及使用SirPython的建议,在有意义的时候内联,并且必须在MainGame中创建一个方法来获取剩余的地雷)。
@Override
public void start(Stage stage) throws Exception {
NumberDisplay mineCount = new NumberDisplay(DIGITS);
MainGame mainGame = createMainGame(mineCount);
Parent mainLayout = createLayout(mainGame, mineCount);
startGame(stage, mainLayout);
}接下来是MainGame,我不想看到深度嵌套的方法。我宁愿在最多第二个巢里面看到一种方法。就会变成这样
private void assignEvent(Board board){
for (ImageView[] cellRow: this.getCell()){
for (ImageView cell: cellRow){
cell.setOnMouseClicked(e -> {
int[] index = getClickedIndex(cell, board);
int x = index[0];
int y = index[1];
if (e.getButton().equals(MouseButton.SECONDARY)){
if (!(board.getCell(x, y).isSelected())){
flag(x, y, board);
}
} else {
if (!(board.getCell(x,y).isFlagged())){
selectCell(x, y, x, y, board);
if (board.getBoardSize() - board.getNumMine() == Cell.getNumSelectedCell()){
win();
}
}
}
});
}
}
}变成这样的事情
private void assignEvent(Board board){
for (ImageView[] cellRow: this.getCell()){
assignEventToCellRow(board, cellRow);
}
}
private void assignEventToCellRow(Board board, ImageView[] cellRow) {
for (ImageView cell: cellRow){
cell.setOnMouseClicked(getMouseEventEventHandler(board, cell));
}
}
private EventHandler<MouseEvent> getMouseEventEventHandler(Board board, ImageView cell) {
return e -> {
//removed for brevity
};
}下面这个方法每次读到都会让我生气
private void selectSurroundingCell(int firstX, int firstY, int x, int y, Board board){
for (int i = (y - 1); i <= (y + 1); i++){
for (int j = (x - 1); j <= (x + 1); j++){
try {
if (board.getCell(j, i).isSelected()){
continue;
}
if (i == y && j == x){
continue;
}
selectCell(firstX, firstY, j, i, board);
} catch (IndexOutOfBoundsException ex){
continue;
}
}
}
}为什么?!?为什么要捕获超出界限的索引异常。如果抛出的话,就只能怪程序员了。这就引出了我接下来要讨论的话题。使用单元测试。当您有某种“复杂”的东西时,必须进行单元测试。有大量的论文,博客和书籍,关于为什么和如何如此,所以我不打算详细说明。不过,我会举出几个例子。我学到的关于单元测试的一个技巧是,它们应该是整洁的、小的和快速的。要知道如何编写测试,请后退一步,并分析您想要写的内容。它能很容易地被拉到一个类中,这样就可以在隔离的情况下进行测试。在这种情况下是的!然而,由于这是如何编写的,将事物提取到适当的类中可能是很棘手的。(因为它是如此的棘手,所以我转到了更容易理解的东西上),NumberDisplay (以及扩展的TimeDisplay)是一个很好的例子,说明了一个类做得太多,并且可以轻松地将它的工作委托给其他类。更重要的是,你可以把这项工作放到一个单元测试中。NumberDisplay做了很多数学运算,只显示3个数字,本质上只是一个数字格式。我们可以利用这个优势,像这样想它。创建一个扩展NumberDisplayDigitFormatter的NumberFormat,它将在测试文件夹中进行匹配的测试。
public class NumberDisplayDigitFormatter extends NumberFormat {
@Override
public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
return null;
}
@Override
public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) {
return null;
}
@Override
public Number parse(String source, ParsePosition parsePosition) {
return null;
}
}public class NumberDisplayDigitFormatterTest {
@Test
public void testFormat() throws Exception {
}
}断言过程中的每一行都是我必须运行测试、观察其失败、编辑NumberFormatter并运行测试以查看其通过的地方。最后出来的是这个(先考)
public class NumberDisplayDigitFormatterTest {
@Test
public void testFormat() throws Exception {
NumberDisplayDigitFormatter formatter = new NumberDisplayDigitFormatter();
assertEquals(formatter.format(1), "001");
assertEquals(formatter.format(1234), "234");
assertEquals(formatter.format(-1), "-01");
assertEquals(formatter.format(-123), "-23");
}
}
public class NumberDisplayDigitFormatter extends NumberFormat {
@Override
public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
throw new IllegalStateException("Does not support formatting decimal numbers");
}
@Override
public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) {
String format = "000" + Math.abs(number);
format = format.substring(format.length()-3);
toAppendTo.append(format);
if(number < 0)
toAppendTo.setCharAt(0, '-');
return toAppendTo;
}
@Override
public Number parse(String source, ParsePosition parsePosition) {
throw new IllegalStateException("Does not support parsing numbers");
}
}测试运行在大约300毫秒,我花了大约2分钟来做这个测试。300 may看起来可能很多,但这包括编译时间、安装时间和执行时间。格式化的实际时间是非常小的。
现在,我有了一个字符串,它的格式化方式与您希望NumberDisplay显示它的方式相同。您将在构造函数中传递您的编号格式化程序,并使用setNumber,它将通过格式化程序运行该数字,并根据需要提供适当的图像。(我在我的个人笔记本上有这段代码,所以也许这对你来说是个练习)说到这一点,在我不需要首先改变你所有的图像来使用getClass().getResource("/image/digitalnumber/asdf.png")之前,我就无法工作了。我相信使用它代替硬编码的字符串文字是很好的做法。我甚至将所有图像提取到一个静态加载的抽象ImageResources类中。
public abstract class ImageResources {
public static DigitalNumberImageResources clock = new DigitalNumberImageResources();
public static MineNumberImageResources mines = new MineNumberImageResources();
static Image loadImage(String folder, String imageName){
return new Image(ImageResources.class
.getClass()
.getResource(folder + imageName)
.toExternalForm());
}
static class DigitalNumberImageResources{
private static final String folder = "/images/digitalnumber/";
public static Image one = loadImage(folder, "one.png");
//...
public static Image nine = loadImage(folder, "nine.png");
}
static class MineNumberImageResources {
private static final String folder = "/images/mines/";
public static Image one = loadImage(folder, "one.png");
//...
public static Image eight = loadImage(folder, "eight.png");
}
}使用它只是一个简单的问题,ImageResources.mines.one
所以这就是我现在拥有的。对不起,我没有机会给你看我的另一堂课,我没时间了。
https://codereview.stackexchange.com/questions/96559
复制相似问题