2
\$\begingroup\$

I created a Java Swing Minesweeper, for which I would like some feedback. Note : I'm just in high school so please excuse any 'dumb' ways I may have coded certain segments.

Any tips or books on how to improve the general quality of the code, or any information that will help me become better are deeply appreciated.

Apart from feedback, I would also like to know how to preserve colours in buttons which I have disabled. In the context that, to make an already clicked cell unclickable again, I just disabled it. Or, is there a better way to do the same task?

The blueprint for the code is :

  1. Real Board(buttons[][] for the user to click).

  2. MyBoard (back-end to configure number counts of each cell, etc.

  3. Methods to handle each event of the game.

Please excuse any ambiguity in language.

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
    }

}
\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

Introduction

Your answer reminded me of the time I was in high school, writing things like you did, with Swing and stuff. I decided to re-live that time and dedicated quite some time to write a comprehensive answer and explaining all major decisions made.

Points to improve upon

  1. You implement MouseListener but never actually use it. I'd remove that completely. Using the ActionListener suffices. Unless you add the functionality for flagging mines. Which seems to be a future addition. Read on to see a more elegant solution.
  2. Minesweeper extends JFrame but ends up using a local one. What was the use of inheriting it in the first place?
  3. You maintain two separate arrays for maintaining the counts and the Buttons. You can greatly improve the general readability of the code and its ease of use (and maintenance) if you apply some OOP here. My solution is to make a class called Cell that extends JButton and adds location and count storage.
  4. The way you map \$(x, y)\$ coordinate pairs to integers is non-general. Why the \$100\$? It seems arbitrary. In fact, you can achieve the same thing (unique mapping) by using size instead of \$100\$. It can also generalize to cases when the size of the grid extends beyond \$100\$, where using a fixed constant would stop resulting in unique numbers.
  5. There is a lot of duplication of code. Especially when you check whether a cell's neighbors are mines or zero-valued. I presented a solution where you obtain all the valid neighbors and perform operations on them.
  6. The dominoEffect method is not named according to the guideline of having methods named as verbs or verb phrases. cascade is the one I used. Also, takeTheW and takeTheL might be fine but I don't prefer them. Always prefer names which describe what the method is doing.
  7. The recursion you implemented is tail-recursive, which means it can be replaced by an appropriate loop, thereby avoiding a lot of overhead.
  8. The ActionLister (and later, the MouseListener) interface can be implemented anonymously and stored in a variable. This reduces the clutter.
  9. Prefer booleans when you are choosing between only two possible outcomes for an integer. Case in point: the parameter m in takeTheL().
  10. Use better data structures when you are relying on operations that are carried out frequently to be carried out efficiently. I'd suggest replacing your array list with Sets.
  11. Swing Applications should be launched on a separate thread. Refer to my main method in the refactored code.
  12. There are a few more converns but I've included them all in the refactored code that I present in the next section.

The Refactored Program

Admittedly, I skimped on the comments, but I believe the code is readable and self-explanatory. Leave a comment if you don't understand a particular snippet.

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));
    }
}
\$\endgroup\$
6
  • \$\begingroup\$ Thank you so much for the effort taken. I go ahead of what we learn in school out of interest, and so a lot of my OOP and structuring data is rather half-baked. I learnt a lot from your refactored code. It is especially readable and consists of concise ways to do what I did. Cheers and thanks again :-) \$\endgroup\$ Mar 12, 2019 at 11:41
  • \$\begingroup\$ Also, do you have any suggestions for books or online sources which could help me understand intricacies of OOP and structuring data? \$\endgroup\$ Mar 12, 2019 at 11:46
  • \$\begingroup\$ I formed a question. With regard to this program, is there any other advantage to using sets rather than lists other than the fact that sets are unordered and don't allow duplicates? Also, I just realized that I needn't stop the user from making repeated moves on a single cell, as that has no reason to be illegal. Rather, it harms the player. \$\endgroup\$ Mar 12, 2019 at 11:48
  • \$\begingroup\$ @greyothello The more you write code, the better you will be at recognizing code that is repetitive and could be packaged neatly as an object. If you need a list of books, I'd say this list seems good. \$\endgroup\$ Mar 12, 2019 at 15:16
  • \$\begingroup\$ @greyothello The advantage of using a set here is faster insertion and deletion time. Look up the time complexities for operations on Different Data Structures. And your statement on prevent users from making repeated moves seems unclear. Once an area is cleared, you definitely can't "unclear" it. \$\endgroup\$ Mar 12, 2019 at 15:18
0
\$\begingroup\$

First step would be to study the MVC design pattern. Now you have all your code in one file. Separate the logic of the game into a separate "minesweeper engine" class that is not dependant on the Swing framework. Make the engine send events to your user interface.

As to code style, take a look at some of the Java-tagged posts here to get an idea about how to format code and declare variables (you're not following too many known good practises).

\$\endgroup\$
1
  • \$\begingroup\$ Yes I admit takeTheL and takeTheW were a little naughty, for instance, but I certainly will look up the posts you pointed to, Thank you :-) \$\endgroup\$ Mar 12, 2019 at 11:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.