Implementing the MVC model

2019-09-14 17:52发布

问题:

Whenever I need help here on SO, I'm asked to post code which follows the MVC model. So went online, read some subject regarding the model but still can't yet understand properly this concept. With the below code for example, I'm trying to adapt it to the MVC model. Does anyone have an idea how to achieve this?

The codes below draw a 2D array grid, paint oval inside cells, search for cells' neighbors, find a path through cells then draw lines passing by all cell within the path.

It includes test cases to help you better understand how it works.

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Line2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Stack;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;


public final class Pha extends JFrame {

    //a collection of cells in the path.
    //each cell represented by [row,col]
    private Stack<int[]> path;
    // legth 
    //a path shorter than min can not surround any cell
    private static final int MIN_PATH_LENGTH = 4;

    //a collection of cells that has been tested
    private ArrayList<int[]> checked;

    //represents the cell where the search starts from
    int[] origin;
    //represents the token of the origin
    Token originToken;

    private static int ROWS = 15;
    private static int COLS = ROWS;
    private static int cellSize = 15;
    private static int canvasWidth = (cellSize * COLS) + (ROWS *4) ;
    private static int canvasHeight = cellSize * ROWS ;
    private static int gridWidth = 1;
    private static int halfGridWidth = gridWidth / 2;

    private static int cellPadding = cellSize / 5;
    private static int symbolSize = cellSize - (cellPadding * 2);
    private static int symbolStrokeWidth = 3;

    private enum Token{
        VIDE, CERCLE_ROUGE, CERCLE_BLEU
    }

    private Token[][] board;
    private final DrawCanvas canvas;
    private GameState actualState;
    public enum GameState{
        JOUE, NUL, CERCLE_ROUGE_GAGNE, CERCLE_BLEU_GAGNE
    }
    private Token actualPlayer;

    //used to set different test data
    private static int testNumber = 0;

    public Pha(){
        canvas = new DrawCanvas();
        canvas.setPreferredSize(new Dimension(canvasWidth, canvasHeight));

       canvas.addMouseListener(new MouseAdapter(){
            @Override
            public void mouseClicked(MouseEvent e){
                int x = e.getX();
                int y = e.getY();

                int selectedRow = y / cellSize;
                int selectedCol = x / cellSize;

                if(actualState == GameState.JOUE){
                    if(selectedRow >= 0 && selectedRow < ROWS && selectedCol >= 0
                            && selectedCol < COLS && board[selectedRow][selectedCol] == Token.VIDE){
                        board[selectedRow][selectedCol] = actualPlayer;
                        updateGame(actualPlayer, selectedRow, selectedCol);
                        actualPlayer = (actualPlayer == Token.CERCLE_BLEU) ? Token.CERCLE_ROUGE : Token.CERCLE_BLEU;

                         findPath(new int[]{selectedRow, selectedCol});
                    }
                }else{
                    initGame();
                }
                repaint();
            }


        });


        Container cp = getContentPane();
        cp.setLayout(new BorderLayout());
        cp.add(canvas, BorderLayout.EAST);

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        pack();
        setTitle("Pha par esQmo");
        setVisible(true);

        board = new Token[ROWS][COLS];
        initGame();

        //fill some data for testing
        int[] origin = loadtestData(board);
        //int[] origin = new int[] {this.getX(),this.getY()};

        findPath(origin);

    }

    private void initGame(){

        for(int ligne = 0; ligne < ROWS; ++ligne){
            for(int colonne = 0; colonne < COLS; ++colonne){
                board[ligne][colonne] = Token.VIDE;

            }
        }
        actualState = GameState.JOUE;
        actualPlayer = Token.CERCLE_ROUGE;
    }
    public void updateGame(Token theSeed, int ligneSelectionnee, int colonneSelectionnee) {
     /* if (aGagne(theSeed, ligneSelectionnee, colonneSelectionnee)) {  // check for win
         actualState= (theSeed == Token.CERCLE_ROUGE) ? GameState.CERCLE_ROUGE_GAGNE : GameState.CERCLE_BLEU_GAGNE;
      } else if (estNul()) {  // check for draw
         actualState = GameState.CERCLE_BLEU_GAGNE;       
      }*/
      // Otherwise, no change to current state (still GameState.PLAYING).
   }


    //search for a path
    private void findPath(int[] origin) {

        //initialize path and checked
        path = new Stack<>();
        this.origin = origin;

        int row = origin[0], col =  origin[1];

        //represents the token of the origin
        originToken = board[row][col];

        //initialize list of checked items
        checked = new  CellsList();

        boolean found = findPath(row, col);

        if(found) {
            printPath();
        } else {
            System.out.println("No path found");
        }
    }

    //recursive method to find path. a cell is represented by its row, col
    //returns true when path was found
    private boolean findPath(int row, int col) {

        //check if cell has the same token as origin
        if(board[row][col] != originToken) {
            return false;
        }

        int[] cell = new int[] {row, col};

        //check if this cell was tested before to avoid checking again
        if(checked.contains(cell)) {
            return false;
        }

        //get cells neighbors
        CellsList neighbors = getNeighbors(row, col);

        //check if solution found. If path size > min and cell
        //neighbors contain the origin, it means that path was found
        if((path.size() > MIN_PATH_LENGTH) && neighbors.contains(origin) ) {

            path.add(cell);
            return true;
        }

        //add cell to checked
        checked.add(cell);

        //add cell to path
        path.add(cell);

        //if path was not found check cell neighbors
        for(int[] neighbor : neighbors ) {

            boolean found = findPath(neighbor[0],neighbor[1]);
            if(found) {
                return true;
            }
        }

        //path not found
        path.pop(); //remove last element from stack
        return false;
    }

    //return a list of all neighbors of cell row, col
    private CellsList getNeighbors(int  row, int col) {

        CellsList neighbors = new CellsList();

        for (int colNum = col - 1 ; colNum <= (col + 1) ; colNum +=1  ) {

            for (int rowNum = row - 1 ; rowNum <= (row + 1) ; rowNum +=1  ) {

                if(!((colNum == col) && (rowNum == row))) {

                    if(withinGrid (rowNum, colNum )  ) {

                        neighbors.add(new int[] {rowNum, colNum});

                    }
                }
            }
        }

        return neighbors;
    }

    private boolean withinGrid(int colNum, int rowNum) {

        if((colNum < 0) || (rowNum <0) ) {
            return false;
        }
        if((colNum >= COLS) || (rowNum >= ROWS)) {
            return false;
        }
        return true;
    }

    private void printPath() {
        System.out.print("Path : " );
        for(int[] cell : path) {
            System.out.print(Arrays.toString(cell));
        }
        System.out.println("");

    }



    class DrawCanvas extends JPanel{

        @Override
        public void paintComponent(Graphics g){
            super.paintComponent(g);
            setBackground(Color.WHITE);

            g.setColor(Color.BLACK);
            for(int ligne = 1; ligne < ROWS; ++ligne){
                g.fillRoundRect(0, (cellSize * ligne) - halfGridWidth, canvasWidth - 1,
                        gridWidth, gridWidth, gridWidth);
            }
            for(int colonne = 1; colonne < COLS; ++colonne){
                g.fillRoundRect((cellSize * colonne) - halfGridWidth, 0
                        , gridWidth, canvasHeight - 1,
                        gridWidth, gridWidth);
            }


            Graphics2D g2d = (Graphics2D)g;
            g2d.setStroke(new BasicStroke(symbolStrokeWidth,
                    BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
            for(int ligne = 0; ligne < ROWS; ++ligne){
                for(int colonne = 0; colonne < COLS; ++colonne){
                    int x1 = (colonne * cellSize) + cellPadding;
                    int y1 = (ligne * cellSize) + cellPadding;

                    if(board[ligne][colonne] == Token.CERCLE_ROUGE){
                        g2d.setColor(Color.RED);
                        g2d.drawOval(x1, y1, symbolSize, symbolSize);
                        g2d.fillOval(x1, y1, symbolSize, symbolSize);
                    } else
                        if(board[ligne][colonne] == Token.CERCLE_BLEU){
                            int x2 = (colonne * cellSize) + cellPadding;
                            g2d.setColor(Color.BLUE);
                            g2d.drawOval(x1, y1, symbolSize, symbolSize);
                            g2d.fillOval(x2, y1, symbolSize, symbolSize);
                        }
                }
            }


            //draw lines
           /* Stack<int[]> drawingPath = new Stack<>();
            drawingPath.addAll(path);
            drawingPath.add(drawingPath.get(0));


            Point startPoint = null;
            for (int[] cell : path){
             Point endPoint = new Point((cell[1] * cellSize) + cellSize / 2, (cell[0] * cellSize) + cellSize / 2);

            if (startPoint != null) {
            //Graphics2D g2d = (Graphics2D) g;
            g2d.setStroke(new BasicStroke(symbolStrokeWidth,
                    BasicStroke.CAP_SQUARE, BasicStroke.CAP_BUTT));
            g2d.draw(new Line2D.Double(startPoint, endPoint));

            }
            startPoint = endPoint;
            } 
         */
    Stack<int[]> drawingPath = new Stack<>();
    drawingPath.addAll(path);
    drawingPath.add(drawingPath.get(0));

    Point startPoint = null;
    for (int[] cell : drawingPath) {
        System.out.println(cell[1] + "," + cell[0]);
        Point endPoint = new Point((cell[1] * cellSize) + cellSize / 2, (cell[0] * cellSize) + cellSize / 2);
        if (startPoint != null) {
            g2d.draw(new Line2D.Double(startPoint, endPoint));
        }
        startPoint = endPoint;
    }

        }
    }

    public static void main(String[] args){

        //set test number. Change values between 0-2 to run different tests
        testNumber = 2;
        SwingUtilities.invokeLater(() -> {
           // new Pha();
            Pha pha = new Pha();
        });
    }

    //method used for testing only: load test data
    private static int[] loadtestData(Token[][] board) {

        switch (testNumber) {

            case 1:
                board[6][6] = Token.CERCLE_ROUGE; //origin and target
                board[6][7] = Token.CERCLE_ROUGE;
                board[6][8] = Token.CERCLE_BLEU;
                board[9][9] = Token.CERCLE_BLEU;

                board[7][6] = Token.CERCLE_ROUGE;
                board[7][7] = Token.CERCLE_BLEU;
                board[7][8] = Token.CERCLE_BLEU;

                board[8][6] = Token.CERCLE_ROUGE;
                board[8][7] = Token.CERCLE_ROUGE;
                board[8][8] = Token.CERCLE_ROUGE;

                board[5][7] = Token.CERCLE_ROUGE;
                board[5][8] = Token.CERCLE_ROUGE;
                board[5][9] = Token.CERCLE_ROUGE;
                board[6][9] = Token.CERCLE_ROUGE;
                board[7][9] = Token.CERCLE_ROUGE;
                return new int[] {6,6};

            case 2:
                //line 3
                board[3][6] = Token.CERCLE_ROUGE;
                //line 4
                board[4][4] = Token.CERCLE_BLEU; //origin
                board[4][5] = Token.CERCLE_BLEU;
                board[4][6] = Token.CERCLE_BLEU;
                board[4][8] = Token.CERCLE_BLEU;
                //line5
                board[5][3] = Token.CERCLE_BLEU;
                board[5][5] = Token.CERCLE_ROUGE;
                board[5][7] = Token.CERCLE_BLEU;
                board[5][8] = Token.CERCLE_ROUGE;
                board[5][9] = Token.CERCLE_BLEU;
                //line 6
                board[6][2] = Token.CERCLE_BLEU;
                board[6][3] = Token.CERCLE_ROUGE;
                board[6][4] = Token.CERCLE_ROUGE;
                board[6][5] = Token.CERCLE_ROUGE;
                board[6][6] = Token.CERCLE_ROUGE;
                board[6][7] = Token.CERCLE_ROUGE;
                board[6][8] = Token.CERCLE_ROUGE;
                board[6][9] = Token.CERCLE_BLEU;
                //line 7
                board[7][3] = Token.CERCLE_BLEU;
                board[7][4] = Token.CERCLE_BLEU;
                board[7][5] = Token.CERCLE_BLEU;
                board[7][6] = Token.CERCLE_BLEU;
                board[7][7] = Token.CERCLE_ROUGE;
                board[7][8] = Token.CERCLE_BLEU;
                //line 8
                board[8][3] = Token.CERCLE_ROUGE;
                board[8][7] = Token.CERCLE_BLEU;
                board[8][8] = Token.CERCLE_ROUGE;
                board[8][9] = Token.CERCLE_BLEU;
                //line 9
                board[9][7] = Token.CERCLE_ROUGE;
                board[9][8] = Token.CERCLE_BLEU;
                board[9][9] = Token.CERCLE_ROUGE;
                //line 10
                board[10][8] = Token.CERCLE_ROUGE;
                board[10][9] = Token.CERCLE_ROUGE;

                return new int[] {4,4};

            case 0: default:
                    board[6][6] = Token.CERCLE_ROUGE;
                    board[6][7] = Token.CERCLE_ROUGE; //origin and target
                    board[6][8] = Token.CERCLE_BLEU;

                    board[7][6] = Token.CERCLE_ROUGE;
                    board[7][7] = Token.CERCLE_BLEU;
                    board[7][8] = Token.CERCLE_ROUGE;

                    board[8][6] = Token.CERCLE_ROUGE;
                    board[8][7] = Token.CERCLE_ROUGE;
                    board[8][8] = Token.CERCLE_ROUGE;

                    board[5][7] = Token.CERCLE_ROUGE;
                    board[5][8] = Token.CERCLE_ROUGE;
                    board[5][9] = Token.CERCLE_ROUGE;
                    board[6][9] = Token.CERCLE_ROUGE;
                    board[7][9] = Token.CERCLE_ROUGE;
                    return new int[] {6,7};
        }
    }
}

class CellsList extends ArrayList<int[]>{

    @Override  //override to check by the value of int[]
    public boolean contains(Object o) {

        for (int[] a : this) {
            if(Arrays.equals(a, (int[]) o)) {
                return true;
            }
        }
        return false;
    }
}

回答1:

My answer is based on the MCV model suggested by Hovercraft Full Of Eels in this post.
I made an effort to simplify it a little, added some comments, and adapted it to your code.
I also added path finding calculation.
The code contains 7 classes. I had each class in a separate file, all live in the same folder :


Main class

import javax.swing.SwingUtilities;

public class MvcPha {

    public static void main(String[] args) {

        // run all on the Swing event thread
        SwingUtilities.invokeLater(() -> {
            Model model = new Model();
            View view = new View();
            new Control(model, view);
        });
    }
}

Control class

import java.beans.IndexedPropertyChangeEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.SwingUtilities;
public class Control {

    private Model model;
    private View view;
    private Token lastToken;

    public Control(Model model, View view) {

        this.model = model;
        this.view = view;
        lastToken = Token.CERCLE_BLEU;

        view.createGrid(model.getRows(), model.getCols());

        view.addPropertyChangeListener(new ViewListener());
        model.addPropertyChangeListener(Model.TOKEN, new ModelListener());
        view.start();
    }

    //a listener added to view panel to listen to property  changes events
    //fired by the mouse listener of each cell
    private class ViewListener implements PropertyChangeListener {
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            if (evt.getPropertyName().equals(View.CELL_SELECTION)) {
                int row = view.getSelectedRow();
                int col = view.getSelectedCol();

                Token token = model.getToken(row, col);
                if (token == Token.VIDE) {

                    lastToken = (lastToken == Token.CERCLE_BLEU) ?
                            Token.CERCLE_ROUGE : Token.CERCLE_BLEU;
                    token = lastToken;
                }
                model.setToken(token, row, col);
            }
        }
    }

    //listener added to model to listen to token changes. used to updated view
    //when token changes
    private class ModelListener implements PropertyChangeListener {
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            IndexedPropertyChangeEvent iEvt = (IndexedPropertyChangeEvent)evt;
            int index = iEvt.getIndex();
            int row = index / Model.COLS;
            int col = index % Model.COLS;
            Token token = (Token) evt.getNewValue();

            SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {

                    view.setCell(token, row, col);
                    view.setPath(model.getPath());
                    view.refresh();
                }
            });
        }
    }
}

Model class

import java.beans.PropertyChangeListener;
import javax.swing.event.SwingPropertyChangeSupport;

public class Model {

    public static final int ROWS = 20;
    public static final int COLS = ROWS;
    public static final String TOKEN = "token";
    private Token[][] grid = new Token[ROWS][COLS];
    private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);

    //a stack representing cells in the path
    private Path path;

    public Model() {

        //set entire grid to  Token.VIDE
        for (int r = 0; r < grid.length; r++) {
            for (int c = 0; c < grid[r].length; c++) {
                grid[r][c] = Token.VIDE;
            }
        }
    }

    Token getToken(int row, int col) {

        return grid[row][col];
    }

     void setToken(Token token, int row, int col) {

        Token oldValue = grid[row][col];
        Token newValue = token;
        grid[row][col] = token;
        int index = (row * grid[row].length) + col;
        pcSupport.fireIndexedPropertyChange(TOKEN, index, oldValue, newValue);

        findPath(new int[] {row, col});
    }

    void addPropertyChangeListener(String name, PropertyChangeListener listener) {

        pcSupport.addPropertyChangeListener(name, listener);
    }

    int getRows() {
        return ROWS;
    }

    int getCols() {
        return COLS;
    }

    //search for a path
    private void findPath(int[] origin) {

        //initialize path and checked
        path = new Path(grid);
        path.findPath(origin);
    }

    CellsList getPath() {

        return (path == null ) ? null : path.getPath();
    }
}

View class

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeListener;
import java.util.EnumMap;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;


public class View {

    private static final int ICON_W = 18;
    public static final String CELL_SELECTION = "cell selection";
    private int rows;

    private JPanel mainPanel;
    private JLabel[][] grid;
    private Map<Token, Icon> iconMap = new EnumMap<>(Token.class);
    private int selectedRow;
    private int selectedCol;

    //a collection of cells representing a path
    private CellsList path;

    View() {

        iconMap.put(Token.VIDE, createIcon(new Color(0, 0, 0, 0)));
        iconMap.put(Token.CERCLE_BLEU, createIcon(Color.BLUE));
        iconMap.put(Token.CERCLE_ROUGE, createIcon(Color.RED));

        mainPanel = new JPanel();
    }

    private Icon createIcon(Color color) {

        BufferedImage img = new BufferedImage(ICON_W, ICON_W, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2 = img.createGraphics();
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setColor(color);
        g2.fillOval(1, 1, ICON_W - 2, ICON_W - 2);
        g2.dispose();

        return new ImageIcon(img);
    }

    void createGrid(int rows, int cols) {

        MyMouseListener listener = new MyMouseListener();
        setRows(rows);

        mainPanel.setLayout(new GridLayout(rows, cols, 1, 1));
        mainPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
        mainPanel.setBackground(Color.BLACK);
        grid = new JLabel[rows][cols];
        for (int r = 0; r < grid.length; r++) {
            for (int c = 0; c < grid[r].length; c++) {
                grid[r][c] = new JLabel(iconMap.get(Token.VIDE));
                grid[r][c].addMouseListener(listener);
                grid[r][c].setOpaque(true);
                grid[r][c].setBackground(Color.WHITE);
                mainPanel.add(grid[r][c]);
            }
        }
    }

    int getSelectedRow() {
        return selectedRow;
    }

    int getSelectedCol() {
        return selectedCol;
    }

    void setCell(Token token, int row, int col) {

        grid[row][col].setIcon(iconMap.get(token));
    }

    int getRows() {
        return rows;
    }

    void setRows(int rows) {
        this.rows = rows;
    }

    //added to each cell to listen to mouse clicks
    //fires property change with cell index
    private class MyMouseListener extends MouseAdapter {
        @Override
        public void mousePressed(MouseEvent e) {
            JLabel selection = (JLabel) e.getSource();
            for (int r = 0; r < grid.length; r++) {
                for (int c = 0; c < grid[r].length; c++) {
                    if (selection == grid[r][c]) {
                        selectedRow = r;
                        selectedCol = c;
                        int index = (r * grid[r].length) + c;
                        mainPanel.firePropertyChange(CELL_SELECTION, -1, index);
                    }
                }
            }
        }
    }

    void start() {

        JFrame frame = new JFrame("MVC Pha");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(mainPanel);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    //add listener to listen to property changes fired by MyMouseListener
    void addPropertyChangeListener(PropertyChangeListener viewListener) {

        mainPanel.addPropertyChangeListener(viewListener);
    }

    void setPath(CellsList path) {

        this.path = path;
        if(path != null) {
            drawPath();
        }
    }

    //highlight path by changing background color. 
    //It can be changed to draw lines between cells
    private void drawPath() {

        for (int r = 0; r < grid.length; r++) {
            for (int c = 0; c < grid[r].length; c++) {

                if((path != null) && path.contains(new int[] {r,c})) {
                    grid[r][c].setBackground(Color.YELLOW);
                } else {
                    grid[r][c].setBackground(Color.WHITE);
                }
            }
        }
    }

    void refresh() {

        mainPanel.repaint();
    }
}

Path class to handle path finding (still needs some debugging)

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Stack;

//a stack representing cells in the path
//each cell represented by [row,col]
class Path extends Stack<int[]>{

    private Token[][] grid;

    //a path shorter than min can not surround any cell
    private static final int MIN_PATH_LEGTH = 3;

    //a collection of cells that has been tested
    private ArrayList<int[]>checked;

    //represents the cell where the search starts from
    int[] origin;
    //represents the token of the origin
    Token originToken;

    private int rows;
    private int cols;

    Path(Token[][] grid){

        this.grid = grid;
        rows = grid.length;
        cols = grid[0].length;
    }

    //search for a path
    boolean findPath(int[] origin) {

        this.origin = origin;

        int row = origin[0] , col =  origin[1];

        //represents the token of the origin
        originToken = grid[row][col];

        //initialize list of checked items
        checked = new  CellsList();

        boolean found = findPath(row, col);

        if(found) {
            printPath();
        } else {
            System.out.println("No path found");
        }

        return found;
    }

    //recursive method to find path. a cell is represented by its row, col
    //returns true when path was found
    private boolean findPath(int row, int col) {

        //check if cell has the same token as origin
        if(grid[row][col] != originToken) {
            return false;
        }

        int[] cell = new int[] {row, col};

        //check if this cell was tested before to avoid checking again
        if(checked.contains(cell)) {
            return false;
        }

        //get cells neighbors
        CellsList neighbors = getNeighbors(row, col);

        //check if solution found. If path size > min and cell
        //neighbors contain the origin it means that path was found
        if((size() >= MIN_PATH_LEGTH) && neighbors.contains(origin)  ) {

            add(cell);
            return true;
        }

        //add cell to checked
        checked.add(cell);

        //add cell to path
        add(cell);

        //if path was not found check cell neighbors
        for(int[] neighbor : neighbors ) {

            boolean found = findPath(neighbor[0],neighbor[1]);
            if(found) {
                return true;
            }
        }

        //path not found
        pop(); //remove last element from stack
        return false;
    }

    //return a list of all neighbors of cell row, col
    private CellsList getNeighbors(int  row, int col) {

        CellsList neighbors = new CellsList();

        for (int colNum = col - 1 ; colNum <= (col + 1) ; colNum +=1  ) {

            for (int rowNum = row - 1 ; rowNum <= (row + 1) ; rowNum +=1  ) {

                if(!((colNum == col) && (rowNum == row))) {

                    if(withinGrid (rowNum, colNum )  ) {

                        neighbors.add( new int[] {rowNum, colNum});
                    }
                }
            }
        }

        return neighbors;
    }

    private boolean withinGrid(int colNum, int rowNum) {

        if((colNum < 0) || (rowNum <0) ) {
            return false;
        }
        if((colNum >= cols) || (rowNum >= rows)) {
            return false;
        }
        return true;
    }

    //use for testing
    private void printPath() {
        System.out.print("Path : " );
        for(int[] cell : this) {
            System.out.print(Arrays.toString(cell));
        }
        System.out.println("");
    }

    public CellsList getPath() {

        CellsList cl = new CellsList();
        cl.addAll(this);
        return cl;
    }
}

Token enum as in OP

public enum Token {
    VIDE, CERCLE_BLEU, CERCLE_ROUGE
}

A simple collection override

import java.util.ArrayList;
import java.util.Arrays;

class CellsList extends ArrayList<int[]>{

    @Override  //override to check by the value of int[]
    public boolean contains(Object o) {

        for (int[] a : this) {
            if(Arrays.equals(a, (int[]) o)) {
                return true;
            }
        }

        return false;
    }
}

The solution is long, but not very complex. It does need careful studying.
I hope you'll find it helpful.



回答2:

Here there is a related question I found very clear,

MVC (model-view-controller) - can it be explained in simple terms?

The MVC is used to define the roles of different "bolcks" of code. It's similar to a layered structure, applied to your code I would suggest to divide as:

  • Model: the methods that search for neighbours, for the path and everything that belongs to the creational part for drawing the grid.
  • Controller: the methods from which the user interacts and those methods will later call the methods of the Model.
  • View: everything that prints/is printed to the user.


标签: java model