In this program I’m trying to input a functionality to start game, where if I click start game (MainMenu) a new jpanel opens up (MainGame), creating another jpanel that creates jbuttons in a grid. If i go back, and click start game again, a new grid should generate instead of the previous one, effectively starting a “new game”. the problem is that if i go back and click on new game again, the program creates 2 grids.
I’ve tried removing the instance of the grid panel with = null but it doesn’t work
Main function:
import javax.swing.*; import java.awt.*; import java.awt.CardLayout; public class Game extends JFrame { MainMenu mainMenu; Settings settings; MainGame mainGame; CardLayout cl; JPanel container; public Game(){ setSize(900,900); //have all as seperate classes setDefaultCloseOperation(3); //cl call container container = new JPanel(); //container call menu1 and menu2 cl = new CardLayout(); mainMenu = new MainMenu(); settings = new Settings(); mainGame = new MainGame(); mainMenu.setSettings(settings); settings.setMainMenu(mainMenu); settings.setMainGame(mainGame); mainMenu.setMainGame(mainGame); mainGame.setMainMenu(mainMenu); mainGame.setSettings(settings); container.setLayout(cl); //this stays here i think //add setter for main game here container.add(mainMenu,"1"); container.add(settings,"2"); container.add(mainGame,"3"); mainMenu.setContainer(container); mainMenu.setCl(cl); settings.setContainer(container); settings.setCl(cl); mainGame.setContainer(container); mainGame.setCl(cl); cl.show(container, "1"); add(container,BorderLayout.CENTER); } public static void main(String[] args) { Game game = new Game(); game.setVisible(true); } }
main game class:
import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class MainGame extends JPanel { MainMenu mainMenu; Settings settings; CardLayout cl; JPanel container; String rows; String columns; public void setMainMenu(MainMenu mainMenu) { this.mainMenu = mainMenu; } public void setSettings(Settings settings) { this.settings = settings; } public void setCl(CardLayout cl) { this.cl = cl; } public void setContainer(JPanel container) { this.container = container; } public void setRows(String rows) { this.rows = rows; } public void setColumns(String columns) { this.columns = columns; } public MainGame(){ JPanel north = new JPanel(); north.setLayout(new FlowLayout()); ReturnAction returnAl = new ReturnAction(); JButton Return2 = new JButton("Return"); Return2.addActionListener(returnAl); north.add(Return2); add(north); } class ReturnAction implements ActionListener { @Override public void actionPerformed(ActionEvent e) { cl.show(container,"1"); } } }
Main menu class (this one contains the game generation part):
import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Random; public class MainMenu extends JPanel { JPanel grid = new JPanel(); MainGame mainGame; Settings settings; CardLayout cl; JPanel container; String rows; String columns; boolean checkexists = false; int rownumber; int columnnumber; public void setMainGame(MainGame mainGame) { this.mainGame = mainGame; } public void setCl(CardLayout cl) { this.cl = cl; } public void setContainer(JPanel container) { this.container = container; } public void setSettings(Settings settings) { this.settings = settings; } public void setRows(String rows) { this.rows = rows; } public void setColumns(String columns) { this.columns = columns; } public MainMenu() { setLayout(new GridLayout(3, 1)); JButton Newgame = new JButton("New Game"); JButton Cont = new JButton("Continue"); JButton Sett = new JButton("Settings"); add(Newgame); add(Cont); SwitchMenu1 switchMenu1 = new SwitchMenu1(); SwitchMenu2 switchMenu2 = new SwitchMenu2(); GenerateGame generateGame = new GenerateGame(); Newgame.addActionListener(switchMenu2); Newgame.addActionListener(generateGame); Sett.addActionListener(switchMenu1); add(Sett); } class SwitchMenu1 implements ActionListener { @Override public void actionPerformed(ActionEvent e) { cl.show(container, "2"); } } class SwitchMenu2 implements ActionListener { @Override public void actionPerformed(ActionEvent e) { cl.show(container, "3"); } } class GenerateGame implements ActionListener { @Override public void actionPerformed(ActionEvent e) { if (checkexists == true){ grid = null; grid = new JPanel(); } try { rownumber = Integer.parseInt(rows); } catch (NumberFormatException be) { rownumber = 10; } try { columnnumber = Integer.parseInt(columns); } catch (NumberFormatException be) { columnnumber = 10; } Random rand = new Random(); int randomnumber; String Letters = "AAIIOOUUEEABCDEFGHIJKLMNOPQRSTUVWXYZ"; grid.setLayout(new GridLayout(rownumber, columnnumber)); JButton[][] gridbutton = new JButton[rownumber][columnnumber]; MainbuttonAL mainbuttonAL = new MainbuttonAL(); for (int i = 0; i < rownumber; i++) { for (int j = 0; j < columnnumber; j++) { if (checkexists == true){ gridbutton[i][j] = null; } randomnumber = rand.nextInt(Letters.length()); gridbutton[i][j] = new JButton("" + Letters.charAt(randomnumber)); gridbutton[i][j].addActionListener(mainbuttonAL); grid.add(gridbutton[i][j]); } } mainGame.add(grid); checkexists = true; } } class MainbuttonAL implements ActionListener { @Override public void actionPerformed(ActionEvent e) { } } }
settings class:
import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Random; public class MainMenu extends JPanel { JPanel grid = new JPanel(); MainGame mainGame; Settings settings; CardLayout cl; JPanel container; String rows; String columns; boolean checkexists = false; int rownumber; int columnnumber; public void setMainGame(MainGame mainGame) { this.mainGame = mainGame; } public void setCl(CardLayout cl) { this.cl = cl; } public void setContainer(JPanel container) { this.container = container; } public void setSettings(Settings settings) { this.settings = settings; } public void setRows(String rows) { this.rows = rows; } public void setColumns(String columns) { this.columns = columns; } public MainMenu() { setLayout(new GridLayout(3, 1)); JButton Newgame = new JButton("New Game"); JButton Cont = new JButton("Continue"); JButton Sett = new JButton("Settings"); add(Newgame); add(Cont); SwitchMenu1 switchMenu1 = new SwitchMenu1(); SwitchMenu2 switchMenu2 = new SwitchMenu2(); GenerateGame generateGame = new GenerateGame(); Newgame.addActionListener(switchMenu2); Newgame.addActionListener(generateGame); Sett.addActionListener(switchMenu1); add(Sett); } class SwitchMenu1 implements ActionListener { @Override public void actionPerformed(ActionEvent e) { cl.show(container, "2"); } } class SwitchMenu2 implements ActionListener { @Override public void actionPerformed(ActionEvent e) { cl.show(container, "3"); } } class GenerateGame implements ActionListener { @Override public void actionPerformed(ActionEvent e) { if (checkexists == true){ grid = null; grid = new JPanel(); } try { rownumber = Integer.parseInt(rows); } catch (NumberFormatException be) { rownumber = 10; } try { columnnumber = Integer.parseInt(columns); } catch (NumberFormatException be) { columnnumber = 10; } Random rand = new Random(); int randomnumber; String Letters = "AAIIOOUUEEABCDEFGHIJKLMNOPQRSTUVWXYZ"; grid.setLayout(new GridLayout(rownumber, columnnumber)); JButton[][] gridbutton = new JButton[rownumber][columnnumber]; MainbuttonAL mainbuttonAL = new MainbuttonAL(); for (int i = 0; i < rownumber; i++) { for (int j = 0; j < columnnumber; j++) { if (checkexists == true){ gridbutton[i][j] = null; } randomnumber = rand.nextInt(Letters.length()); gridbutton[i][j] = new JButton("" + Letters.charAt(randomnumber)); gridbutton[i][j].addActionListener(mainbuttonAL); grid.add(gridbutton[i][j]); } } mainGame.add(grid); checkexists = true; } } class MainbuttonAL implements ActionListener { @Override public void actionPerformed(ActionEvent e) { } } }
what method can i employ to regenerate a grid?
Advertisement
Answer
In general, you should be working on the concept of decoupling your views and your data, this means that you could have a “game model” which could be applied to a view and the view would then modify itself based on model properties, this is commonly known as “model – view – controller”.
The problem is, however, you never remove grid
from it’s parent container when you create a new game
if (checkexists == true){ grid = null; grid = new JPanel(); }
Instead, before you re-intialise the grid
, you should remove from it’s parent container
if (grid != null) { mainGame.remove(grid); grid = null; grid = new JPanel(); }
or you could just remove the components from the grid
panel itself
grid.removeAll();
A different approach…
At all stages you should be trying to decouple you objects and workflows from each other, so that it’s easier to change any one part without having adverse effects on the other parts of the system or workflow.
Looking at you code, for example, the navigation decisions are tightly coupled to each panel/view, but in reality, they shouldn’t know or care about how the navigation works (or the fact that there are other views), they should just do there job.
You can decouple this workflow through the use of delegation (backed by an observer). This basically means that the individual view doesn’t care “how” the navigation works, only that when it makes a request for some action to be taken, it happens.
You should take the time to read through…
But how does this help you? Well, it will help you all the time!
Lets start with the “game” itself. The first thing we need is some kind of container to hold the data base logic for the game, so based on your current code, it might look something like…
public interface GameModel { public int getRows(); public int getColumns(); }
I know, amazing isn’t it, but this interface
would grow to hold the logic required to run your game.
Now, we can apply this to the GamePanel
public class GamePane extends JPanel { public interface Obsever { public void back(GamePane source); } private GameModel model; private Obsever obsever; private JPanel contentPane; private ActionListener buttonActionListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { didTap(e.getActionCommand()); } }; public GamePane(Obsever obsever) { this.obsever = obsever; setLayout(new BorderLayout()); contentPane = new JPanel(); add(new JScrollPane(contentPane)); JButton backButton = new JButton("<< Back"); backButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { obsever.back(GamePane.this); } }); JPanel bannerPane = new JPanel(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.weightx = 1; gbc.anchor = GridBagConstraints.LINE_END; bannerPane.add(backButton, gbc); add(bannerPane, BorderLayout.NORTH); } @Override public Dimension getPreferredSize() { // Bunch of things we could do here, but this basically // acts as a stand in for CardLayout, otherwise the primary // view will be to small return new Dimension(800, 800); } public void setModel(GameModel model) { if (this.model == model) { // Do nothing, nothings changed return; } this.model = model; buildUI(); } protected void buildUI() { contentPane.removeAll(); if (model == null) { return; } String Letters = "AAIIOOUUEEABCDEFGHIJKLMNOPQRSTUVWXYZ"; Random rnd = new Random(); JButton[][] gridbutton = new JButton[model.getRows()][model.getColumns()]; contentPane.setLayout(new GridLayout(model.getRows(), model.getColumns())); //Game.MainMenu.MainbuttonAL mainbuttonAL = new Game.MainMenu.MainbuttonAL(); for (int i = 0; i < model.getRows(); i++) { for (int j = 0; j < model.getColumns(); j++) { int randomnumber = rnd.nextInt(Letters.length()); gridbutton[i][j] = new JButton("" + Letters.charAt(randomnumber)); //gridbutton[i][j].addActionListener(mainbuttonAL); contentPane.add(gridbutton[i][j]); } } } protected void didTap(String action) { } }
Now, the nice “juicy” part is in the buildUI
which is called by setModel
when the model changes. This just re-builds the UI based on the GameModel
properties.
As for the navigation concept, you can see part of it in the GamePane
via its Observer
interface
. I started by creating a seperate class to handle the navigation workflows.
This means that the “how” or “implementation detail” is decoupled or hidden from the other parts of the system. Instead, it makes use of simple observer/delegation workflow.
Each view provides an Observer
(for the what of a better name) which describes the navigation actions it needs performed. For example, both the SettingsPane
and GamePane
simply have back
. They don’t care what came before them, that’s up to the navigation controller to decide.
public class NavigationPane extends JPanel { enum View { MAIN_MENU, GAME, SETTINGS } private CardLayout cardLayout; private GameModel model; private GamePane gamePane; // Just for testing... private Random rnd = new Random(); public NavigationPane() { cardLayout = new CardLayout(); setLayout(cardLayout); add(new MainMenu(new MainMenu.Observer() { @Override public void newGame(MainMenu source) { gamePane.setModel(createModel()); navigateTo(View.GAME); } @Override public void continueGame(MainMenu source) { // Because it's possible to push continue // without starting a game // It might be possible have a "menu" model // which can be used to change the enabled state of // the continue button based on the state of the // game gamePane.setModel(getOrCreateGameModel()); navigateTo(View.GAME); } @Override public void settingsGame(MainMenu source) { navigateTo(View.SETTINGS); } }), View.MAIN_MENU); gamePane = new GamePane(new GamePane.Obsever() { @Override public void back(GamePane source) { navigateTo(View.MAIN_MENU); } }); add(gamePane, View.GAME); add(new SettingsPane(new SettingsPane.Obsever() { @Override public void back(SettingsPane source) { navigateTo(View.MAIN_MENU); } }), View.SETTINGS); navigateTo(View.MAIN_MENU); } protected GameModel createModel() { model = new DefaultGameModel(rnd.nextInt(9) + 2, rnd.nextInt(9) + 2); return model; } protected GameModel getOrCreateGameModel() { if (model == null) { model = createModel(); } return model; } protected void add(Component component, View view) { add(component, view.name()); } protected void navigateTo(View view) { cardLayout.show(this, view.name()); } }
Runnable example…
So, that’s a lot of out-of-context code. The below is basically an example of one possible approach you could take to further reduce some of the clutter and coupling which is growing in your code base at this time.
import java.awt.BorderLayout; import java.awt.CardLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Random; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; public class Test { public static void main(String[] args) { new Test(); } public Test() { EventQueue.invokeLater(new Runnable() { @Override public void run() { JFrame frame = new JFrame(); frame.add(new NavigationPane()); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } }); } public class NavigationPane extends JPanel { enum View { MAIN_MENU, GAME, SETTINGS } private CardLayout cardLayout; private GameModel model; private GamePane gamePane; // Just for testing... private Random rnd = new Random(); public NavigationPane() { cardLayout = new CardLayout(); setLayout(cardLayout); add(new MainMenu(new MainMenu.Observer() { @Override public void newGame(MainMenu source) { gamePane.setModel(createModel()); navigateTo(View.GAME); } @Override public void continueGame(MainMenu source) { // Because it's possible to push continue // without starting a game // It might be possible have a "menu" model // which can be used to change the enabled state of // the continue button based on the state of the // game gamePane.setModel(getOrCreateGameModel()); navigateTo(View.GAME); } @Override public void settingsGame(MainMenu source) { navigateTo(View.SETTINGS); } }), View.MAIN_MENU); gamePane = new GamePane(new GamePane.Obsever() { @Override public void back(GamePane source) { navigateTo(View.MAIN_MENU); } }); add(gamePane, View.GAME); add(new SettingsPane(new SettingsPane.Obsever() { @Override public void back(SettingsPane source) { navigateTo(View.MAIN_MENU); } }), View.SETTINGS); navigateTo(View.MAIN_MENU); } protected GameModel createModel() { model = new DefaultGameModel(rnd.nextInt(9) + 2, rnd.nextInt(9) + 2); return model; } protected GameModel getOrCreateGameModel() { if (model == null) { model = createModel(); } return model; } protected void add(Component component, View view) { add(component, view.name()); } protected void navigateTo(View view) { cardLayout.show(this, view.name()); } } public class MainMenu extends JPanel { public interface Observer { public void newGame(MainMenu source); public void continueGame(MainMenu source); public void settingsGame(MainMenu source); } private Observer observer; public MainMenu(Observer observer) { this.observer = observer; setLayout(new GridBagLayout()); JButton newGameButton = new JButton("New Game"); JButton continueButton = new JButton("Continue"); JButton settingsButton = new JButton("Settings"); GridBagConstraints gbc = new GridBagConstraints(); gbc.fill = GridBagConstraints.HORIZONTAL; gbc.gridwidth = GridBagConstraints.REMAINDER; add(newGameButton, gbc); add(continueButton, gbc); add(settingsButton, gbc); newGameButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { observer.newGame(MainMenu.this); } }); continueButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { observer.continueGame(MainMenu.this); } }); settingsButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { observer.settingsGame(MainMenu.this); } }); } } public class SettingsPane extends JPanel { public interface Obsever { public void back(SettingsPane source); } public SettingsPane(Obsever obsever) { setLayout(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridwidth = GridBagConstraints.REMAINDER; add(new JLabel("All your setting belong to us"), gbc); JButton backButton = new JButton("<< Back"); backButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { obsever.back(SettingsPane.this); } }); add(backButton, gbc); } } public interface GameModel { public int getRows(); public int getColumns(); } public class DefaultGameModel implements GameModel { private int rows; private int columns; public DefaultGameModel(int rows, int columns) { this.rows = rows; this.columns = columns; } @Override public int getRows() { return rows; } @Override public int getColumns() { return columns; } } public class GamePane extends JPanel { public interface Obsever { public void back(GamePane source); } private GameModel model; private Obsever obsever; private JPanel contentPane; private ActionListener buttonActionListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { didTap(e.getActionCommand()); } }; public GamePane(Obsever obsever) { this.obsever = obsever; setLayout(new BorderLayout()); contentPane = new JPanel(); add(new JScrollPane(contentPane)); JButton backButton = new JButton("<< Back"); backButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { obsever.back(GamePane.this); } }); JPanel bannerPane = new JPanel(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.weightx = 1; gbc.anchor = GridBagConstraints.LINE_END; bannerPane.add(backButton, gbc); add(bannerPane, BorderLayout.NORTH); } @Override public Dimension getPreferredSize() { // Bunch of things we could do here, but this basically // acts as a stand in for CardLayout, otherwise the primary // view will be to small return new Dimension(800, 800); } public void setModel(GameModel model) { if (this.model == model) { // Do nothing, nothings changed return; } this.model = model; buildUI(); } protected void buildUI() { contentPane.removeAll(); if (model == null) { return; } String Letters = "AAIIOOUUEEABCDEFGHIJKLMNOPQRSTUVWXYZ"; Random rnd = new Random(); JButton[][] gridbutton = new JButton[model.getRows()][model.getColumns()]; contentPane.setLayout(new GridLayout(model.getRows(), model.getColumns())); //Game.MainMenu.MainbuttonAL mainbuttonAL = new Game.MainMenu.MainbuttonAL(); for (int i = 0; i < model.getRows(); i++) { for (int j = 0; j < model.getColumns(); j++) { int randomnumber = rnd.nextInt(Letters.length()); gridbutton[i][j] = new JButton("" + Letters.charAt(randomnumber)); //gridbutton[i][j].addActionListener(mainbuttonAL); contentPane.add(gridbutton[i][j]); } } } protected void didTap(String action) { } } }