I wrote a simple implementation of the Game of life with java applets.
Here's is the source code for the Applet and the Model.
When I click the button to get the next iteration these Exceptions get thrown.
Z:\GameOfLife>appletviewer driver.html
Exception in thread "AWT-EventQueue-1" java.lang.ArrayIndexOutOfBoundsException:
65
at GameOfLifeApplet.mouseClicked(GameOfLifeApplet.java:63)
at java.awt.Component.processMouseEvent(Component.java:6219)
at java.awt.Component.processEvent(Component.java:5981)
at java.awt.Container.processEvent(Container.java:2041)
at java.awt.Component.dispatchEventImpl(Component.java:4583)
at java.awt.Container.dispatchEventImpl(Container.java:2099)
at java.awt.Component.dispatchEvent(Component.java:4413)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:599)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThre ad.java:269)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThre ad.java:174)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)
Try this code that adds the button once rather than every call to paint(). Note that this source still throws AIOOBE if you click outside the grid (and not on the button), but that seems like a basic logic error you should investigate once the button is fixed.
// <applet code='GameOfLifeApplet' width=580 height=650></applet>
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class GameOfLifeApplet extends Applet implements MouseListener,ActionListener
{
//the x and y coordinates to get the location of the clicked points
private int xCo, yCo;
private int diff_x, diff_y;
private GameOfLife game = new GameOfLife();
private Button nextButton = null;
public void init()
{
setLayout(null);
nextButton = new Button("Next Stage");
diff_x = diff_y = 600 / game.getGridSize();
nextButton.setLocation(250, 575);
nextButton.setSize(120, 30);
// add the button once only!
add(nextButton);
addMouseListener(this);
}
private void drawEmptyGrid(Graphics g)
{
g.setColor(Color.white);
g.fillRect(0,0,600,600);
g.setColor(Color.black);
for(int i=0;i<game.getGridSize();++i)
{
g.drawLine(0,i*diff_x,600,i*diff_x);
g.drawLine(i*diff_x,0,i*diff_x,600);
}
g.setColor(Color.white);
}
public void paint(Graphics g)
{
drawEmptyGrid(g);
g.setColor(Color.red);
for(int i=0;i<game.getGridSize();++i)
{
for(int j=0;j<game.getGridSize();++j)
{
if( game.grid[i][j] )
{
g.fillRect(i*diff_x,j*diff_y,diff_x,diff_y);
}
}
}
g.setColor(Color.white);
}
// This method will be called when the mouse has been clicked.
public void mouseClicked (MouseEvent me) {
// Save the coordinates of the click lke this.
xCo = me.getX();
yCo = me.getY();
int x_init = xCo / diff_x;
int y_init = yCo / diff_y;
System.out.println(x_init + "x" + y_init);
game.grid[x_init][y_init] = true;
//show the results of the click
repaint();
}
// This is called when the mous has been pressed
public void mousePressed (MouseEvent me) {}
// When it has been released
// not that a click also calls these Mouse-Pressed and Released.
// since they are empty nothing hapens here.
public void mouseReleased (MouseEvent me) {}
// This is executed when the mouse enters the applet. it will only
// be executed again when the mouse has left and then re-entered.
public void mouseEntered (MouseEvent me) {}
// When the Mouse leaves the applet.
public void mouseExited (MouseEvent me) {}
public void actionPerformed(ActionEvent evt)
{
// Here we will ask what component called this method
if (evt.getSource() == nextButton)
{
System.out.println("I got clicked!");
game.nextIteration();
repaint();
}
}
}
class GameOfLife
{
private final int GRID_SIZE = 64;
public boolean [][] grid = new boolean[GRID_SIZE][GRID_SIZE];
//default constructor
public GameOfLife()
{
for(int i=0;i<GRID_SIZE;++i)
{
for(int j=0;j<GRID_SIZE;++j)
{
grid[i][j] = false;
}
}
}
public int getGridSize()
{
return GRID_SIZE;
}
public int getLiveNeighbors(int i,int j)
{
int neighbors = 0;
for( int tmp_i = i-1; tmp_i <= i+1; ++tmp_i )
{
for( int tmp_j = j-1; tmp_j <= j+1; ++tmp_j )
{
if( tmp_i < 0 || tmp_i >= GRID_SIZE || tmp_j < 0 || tmp_j >= GRID_SIZE )
{}
else
{
if( grid[tmp_i][tmp_j] )
{
neighbors++;
}
}
}
}
return neighbors;
}
public void nextIteration()
{
boolean [][] newGrid = new boolean[GRID_SIZE][GRID_SIZE];
for(int i=0;i<GRID_SIZE;++i)
{
for(int j=0;j<GRID_SIZE;++j)
{
newGrid[i][j] = grid[i][j];
}
}
for( int i=0;i<GRID_SIZE;++i)
{
for( int j=0;j<GRID_SIZE;++j)
{
int my_neighbors = getLiveNeighbors(i,j);
if( !newGrid[i][j] && my_neighbors == 3)
{
grid[i][j] = true;
}
else if( newGrid[i][j] && ( my_neighbors == 2 || my_neighbors == 3 ) )
{
grid[i][j] = true;
}
else
{
grid[i][j] = false;
}
}
}
System.out.println("Change of assignment");
}
}
Further tips
Don't use AWT components in this millennium, use Swing instead.
Don't use null layouts. The custom rendered area does not need it, and the button should be sized and positioned using a layout manager (with maybe a border to pad it out).
Update
This code implements the 2nd suggestion from above 'use layouts', but leaves it as an exercise for the reader to update the components to something that might be used in this millennium (i.e. Swing).
The source below 'cheats' in a sense to show the GUI at it's natural size. This is tricky to do in an applet, since the size is set by the HTML. But put the GUI into a Swing based JOptionPane and it can be put on-screen, packed to its natural size, in just a couple of lines of code.
Here is what it looks like at the 'natural size' (I played with some numbers, to make the GUI smaller).
// <applet code='GameOfLifeApplet' width=320 height=350></applet>
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class GameOfLifeApplet extends Applet implements ActionListener
{
private Button nextButton = null;
private Ecosystem ecosystem;
public void init()
{
add(getGui());
}
public Component getGui() {
Panel gui = new Panel(new BorderLayout(3,3));
ecosystem = new Ecosystem();
gui.add(ecosystem, BorderLayout.CENTER);
nextButton = new Button("Next Stage");
Panel p = new Panel(new FlowLayout());
p.add(nextButton);
gui.add(p, BorderLayout.SOUTH);
nextButton.addActionListener(this);
return gui;
}
public void actionPerformed(ActionEvent evt)
{
// Here we will ask what component called this method
if (evt.getSource() == nextButton)
{
System.out.println("I got clicked!");
ecosystem.nextIteration();
ecosystem.repaint();
}
}
public static void main(String[] args) {
GameOfLifeApplet gola = new GameOfLifeApplet();
// quick cheat to get it on-screen (packed).
javax.swing.JOptionPane.showMessageDialog(null,gola.getGui());
}
}
class Ecosystem extends Panel implements MouseListener {
private GameOfLife game = new GameOfLife();
//the x and y coordinates to get the location of the clicked points
private int xCo, yCo;
private int diff_x, diff_y;
private int size = 300;
Ecosystem() {
diff_x = diff_y = 600 / game.getGridSize();
setPreferredSize(new Dimension(size,size));
addMouseListener(this);
}
public void nextIteration() {
game.nextIteration();
}
private void drawEmptyGrid(Graphics g)
{
g.setColor(Color.white);
g.fillRect(0,0,size,size);
g.setColor(Color.black);
for(int i=0;i<game.getGridSize();++i)
{
g.drawLine(0,i*diff_x,size,i*diff_x);
g.drawLine(i*diff_x,0,i*diff_x,size);
}
g.setColor(Color.white);
}
public void paint(Graphics g)
{
drawEmptyGrid(g);
g.setColor(Color.red);
for(int i=0;i<game.getGridSize();++i)
{
for(int j=0;j<game.getGridSize();++j)
{
if( game.grid[i][j] )
{
g.fillRect(i*diff_x,j*diff_y,diff_x,diff_y);
}
}
}
g.setColor(Color.white);
}
// This method will be called when the mouse has been clicked.
public void mouseClicked (MouseEvent me) {
Point point = me.getPoint();
// Save the coordinates of the click lke this.
xCo = (int)point.getX();
yCo = (int)point.getY();
int x_init = xCo / diff_x;
int y_init = yCo / diff_y;
System.out.println(x_init + "x" + y_init);
game.grid[x_init][y_init] = true;
//show the results of the click
repaint();
}
// This is called when the mous has been pressed
public void mousePressed (MouseEvent me) {}
// When it has been released
// not that a click also calls these Mouse-Pressed and Released.
// since they are empty nothing hapens here.
public void mouseReleased (MouseEvent me) {}
// This is executed when the mouse enters the applet. it will only
// be executed again when the mouse has left and then re-entered.
public void mouseEntered (MouseEvent me) {}
// When the Mouse leaves the applet.
public void mouseExited (MouseEvent me) {}
}
class GameOfLife
{
private final int GRID_SIZE = 60;
public boolean [][] grid = new boolean[GRID_SIZE][GRID_SIZE];
//default constructor
public GameOfLife()
{
for(int i=0;i<GRID_SIZE;++i)
{
for(int j=0;j<GRID_SIZE;++j)
{
grid[i][j] = false;
}
}
}
public int getGridSize()
{
return GRID_SIZE;
}
public int getLiveNeighbors(int i,int j)
{
int neighbors = 0;
for( int tmp_i = i-1; tmp_i <= i+1; ++tmp_i )
{
for( int tmp_j = j-1; tmp_j <= j+1; ++tmp_j )
{
if( tmp_i < 0 || tmp_i >= GRID_SIZE || tmp_j < 0 || tmp_j >= GRID_SIZE )
{}
else
{
if( grid[tmp_i][tmp_j] )
{
neighbors++;
}
}
}
}
return neighbors;
}
public void nextIteration()
{
boolean [][] newGrid = new boolean[GRID_SIZE][GRID_SIZE];
for(int i=0;i<GRID_SIZE;++i)
{
for(int j=0;j<GRID_SIZE;++j)
{
newGrid[i][j] = grid[i][j];
}
}
for( int i=0;i<GRID_SIZE;++i)
{
for( int j=0;j<GRID_SIZE;++j)
{
int my_neighbors = getLiveNeighbors(i,j);
if( !newGrid[i][j] && my_neighbors == 3)
{
grid[i][j] = true;
}
else if( newGrid[i][j] && ( my_neighbors == 2 || my_neighbors == 3 ) )
{
grid[i][j] = true;
}
else
{
grid[i][j] = false;
}
}
}
System.out.println("Change of assignment");
}
}
Other matters
The code moves the custom painting to a Panel. This gets around the common problems of painting directly to a top-level container. It also allows easy re-use of the same GUI in different containers. In this case both an applet, and for the 'application' (which would usually be put in a frame), a JOptionPane. It is now what is known as a 'hybrid applet/application' (easier for testing).
The custom painted component Ecosystem (shrugs) informs the layout what size it prefers to be. This helps us to avoid needing to set the size or bounds of anything.
The button will be exactly as big as it needs to be.
First, I think you're reading the exception trace wrong. The exception is an ArrayIndexOutOfBoundsException and occurs on line 63 of GameOfLifeApplet.java. That your app is an applet or that the exception occurs on the thread AWT-EventQueue-1 bears no relevance at all.
The root cause is that you've not properly synchronized the model and view's idea of how many cells there are in your grid. At the least, you should consider checking that the user actually clicked inside the grid before accessing the array element.
Related
So far all I have managed to do is combine my two files into one file that creates a GUI with the grid of buttons and runs an instance of minesweeper separately. I need the values from the grid of integers to reflect onto the grid of buttons so that after I click a button it reveals the integer underneath. Any help would be much appreciated.
import javax.swing.*;
import java.awt.event.*;
import java.awt.GridLayout;
import java.awt.Component;
import java.awt.Label;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.JOptionPane;
public class Minesweeper extends JFrame implements MouseListener
{
private JButton [][] gridz;
private int [][] g;
private int max;
public Minesweeper()
{
gridz= new JButton [10][10];
builder();
g=new int[10][10];
setRandom(10);
}
public void setRandom(int max)
{
int z=0;
while(z<max)
{
int r=(int)(Math.random()*g.length);
int c=(int)(Math.random()*g[r].length);
if(g[r][c]==0)
{
g[r][c]=-1;
z++;
}
}
}
public int count(int r, int c)
{
int x=0;
if((r-1)>=0 && (c-1)>=0 && (r-1)<g.length && (c-1)<g.length && g[r-1][c-1]==-1)
{
x++;
}
if((r-1)>=0 && (r-1)<g.length && g[r-1][c]==-1)
{
x++;
}
if((r-1)>=0 && (c+1)>=0 && (r-1)<g.length && (c+1)<g.length && g[r-1][c+1]==-1)
{
x++;
}
if((c-1)>=0 && (c-1)<g.length && g[r][c-1]==-1)
{
x++;
}
if((c+1)>=0 && (c+1)<g.length && g[r][c+1]==-1)
{
x++;
}
if((r+1)>=0 && (c-1)>=0 && (r+1)<g.length && (c-1)<g.length && g[r+1][c-1]==-1)
{
x++;
}
if((r+1)>=0 && (r+1)<g.length && g[r+1][c]==-1)
{
x++;
}
if((r+1)>=0 && (c+1)>=0 && (r+1)<g.length && (c+1)<g.length && g[r+1][c+1]==-1)
{
x++;
}
return x;
}
public void setCounts()
{
for(int r=0; r<g.length; r++)
{
for(int c=0; c<g[r].length; c++)
{
if(g[r][c]==0)
{
g[r][c]=count(r,c);
}
String formatted = String.format("%2d", g[r][c]);
System.out.print(formatted + " ");
}
System.out.println();
}
}
public void builder()
{
setSize(500,500);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new GridLayout(10, 10));
for(int r=0; r<gridz.length; r++)
{
for(int c=0; c<gridz[r].length; c++)
{
gridz[r][c]=new JButton("*");
gridz[r][c].setActionCommand(r+":"+c);
gridz[r][c].addMouseListener(this);
getContentPane().add(gridz[r][c]);
}
}
setVisible(true);
}
public void mousePressed(MouseEvent e)
{
Component c = e.getComponent();
JButton b = (JButton)c;
System.out.println(b.getActionCommand());
String s=b.getActionCommand();
int f=s.indexOf(':');
if(f>=0)
{
String row=s.substring(0, f);
String col=s.substring(f+1);
String text=row+ '!' +col;
b.setText(text);
}
}
public void mouseClicked(MouseEvent e)
{
}
public void mouseEntered(MouseEvent e)
{
}
public void mouseExited(MouseEvent e)
{
}
public void mouseReleased(MouseEvent e)
{
}
public static void main(String[] args)
{
Minesweeper a = new Minesweeper();
a.setCounts();
//JOptionPane.showMessageDialog(null, "BOOM");
}
}
One of the (many) things you want to do, is decouple your code. This means that the "logic" for the "game" should be independent of the "view", meaning that you can easily change the logic without adversely effecting the view.
This is where things like model-view-controller and single responsibility concepts come into play.
What you should (try) and do is start by describing the "data" or "model". What properties does it have and what functionality can be executed by other parties.
Since the models state could change, independently, you'll also need some way to notify interested parties, this is where an observer is helpful.
For example, a model for the game might be represented like...
public interface MineSweeperModel {
public interface Observer {
public void cellValueDidChange(MineSweeperModel model, int col, int row, int value);
}
public int getRows();
public int getColumns();
public int getValueAt(int col, int row);
public void expose(int col, int row);
public void setObserver(Observer observer);
}
Now, that's cool and everything, but what we need is some kind of implementation, which might look something like...
public class DefaultMineSweeperModel implements MineSweeperModel {
private int rows;
private int cols;
private int[][] bombsMap;
private int[][] visibleState;
private Observer observer;
public DefaultMineSweeperModel(int rows, int cols, int maxBombs) {
this.rows = rows;
this.cols = cols;
bombsMap = new int[cols][rows];
visibleState = new int[cols][rows];
Random rnd = new Random();
int count = 0;
while (count < maxBombs) {
int row = rnd.nextInt(rows);
int col = rnd.nextInt(cols);
if (bombsMap[col][row] == 0) {
bombsMap[col][row] = -1;
count++;
}
}
}
#Override
public void setObserver(Observer observer) {
this.observer = observer;
}
#Override
public int getRows() {
return rows;
}
#Override
public int getColumns() {
return cols;
}
#Override
public int getValueAt(int col, int row) {
return visibleState[col][row];
}
#Override
public void expose(int col, int row) {
if (visibleState[col][row] == 0 && bombsMap[col][row] == -1) {
// You've found a mine, might want to do something about it...
visibleState[col][row] = -1;
fireCellValueDidChange(col, row, -1);
} else if (visibleState[col][row] == 0) {
// Empty cell
visibleState[col][row] = 1;
fireCellValueDidChange(col, row, 1);
}
}
protected void fireCellValueDidChange(int col, int row, int value) {
if (observer == null) {
return;
}
observer.cellValueDidChange(this, col, row, value);
}
}
Now, we could just as easily create a "easy", "hard" and "you're going to die" implementations and seed the properties internally, but this basic implementation provides us with enough capacity to configure it.
But why (use a interface)? One of the principles of OO is "information hiding", which is supported by the concept of Polymorphism.
This means we can create any kind of model, with any kind of internal logic, but any one wanting to make use of the model, can easily do so by simply accepting a instance of the interface.
Now, the UI. This is basically responsible for providing a visual representation of the state of the model.
public class MineSweeperPane extends JPanel {
private JButton[] buttons;
private MineSweeperModel model;
public MineSweeperPane(MineSweeperModel model) {
this.model = model;
model.setObserver(new MineSweeperModel.Observer() {
#Override
public void cellValueDidChange(MineSweeperModel model, int col, int row, int value) {
int index = (model.getRows() * row) + col;
if (index >= buttons.length) {
System.err.println("No view for cell # " + col + "x" + row);
return;
}
buttons[index].setText(Integer.toString(value));
}
});
// You should be using ActionListener
MouseListener mouseListener = new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
JComponent source = (JComponent) e.getComponent();
// Here's something they won't teach you...
GridBagConstraints gbc = ((GridBagLayout)getLayout()).getConstraints(source);
System.out.println(gbc.gridx + "x" + gbc.gridy);
// Use class based Integer so we don't get NullPointerException
Integer row = (Integer)source.getClientProperty("row");
Integer col = (Integer)source.getClientProperty("col");
if (row == null || col == null) {
System.err.println("!! Invalid cell");
return;
}
System.out.println("Clicked " + col + "x" + row);
getModel().expose(col, row);
}
};
int rowCount = model.getRows();
int colCount = model.getColumns();
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
buttons = new JButton[rowCount * colCount];
for (int row = 0; row < rowCount; row++) {
for (int col = 0; col < colCount; col++) {
JButton btn = makeButton(col, row, model);
btn.addMouseListener(mouseListener);
gbc.gridx = col;
gbc.gridy = row;
add(btn, gbc);
buttons[row * rowCount + col] = btn;
}
}
}
public MineSweeperModel getModel() {
return model;
}
protected JButton makeButton(int col, int row, MineSweeperModel model) {
JButton btn = new JButton(Integer.toString(model.getValueAt(col, row))) {
#Override
public Dimension getPreferredSize() {
return new Dimension(35, 35);
}
};
btn.setFocusable(false);
btn.setFocusPainted(false);
btn.putClientProperty("row", row);
btn.putClientProperty("col", col);
return btn;
}
}
You will note two things.
I use get/setClientProperty to get and set the cell coordinates for the button within the model. This allows me to seed the int values without needing to format/parse them to/from a String, which is, frankly, messy
I also cheated and demonstrated the ability to pull the buttons grid coordinates directly from the GridBagLayout itself. I prefer to the first, but this is a nice side effect not many people know or make use of it.
When "clicked" (and you should be using an ActionListener, but you're apparently not meant to be using an ActionListener because you've not learnt about them ... which begs the question of, why are you using a JButton when ActionListener is the primary mechanism for monitoring for when it's triggered 🙄), the UI updates the model. If there are any changes to the model, the model's observer is notified, which then allows the UI to update its visual state.
I've not put any logic in to the model, apart from exposing the underlying value of the cell (difference between the visialMap and bombMap), so you'll need to do that
There are a number of ways to achieve this. You could simply include a line in your builder method that matches a button to a value from the g array and stores it in a hashmap or some other data structure like so mineHashMap.put(gridz[r][c], g[r][c]);, this way we can check the hashmap later in the mouse event method to find the correct value from the hashmap like so:
int value = mineHashMap.get(b);
b.setText(value+"");`
If starting from scratch, then a quick and easy/dirty solution would be to extend the JButton class to create a customJBotton which would store the integer value inside the class when you create the custom button, and you could contain other logic in it too. Then later when the button is clicked you can easily get the number in the mouse event like so b.setText(b.getMineValue()); or call a custom method you created in the custom button like so b.revealMine(); which would effectively do this.setText(mineValue);
There are many many other ways to do this, but the two options above would slot into your code quite easily. Custom painting on a JPanel rather than using buttons would also be a smart option but it does require more technical understanding and code.
I have two classes (Sampling and Stacker). The Sampling class (my Main class) is extends JFrame and has a JButton with an ActionListener to open the Stacker class.
The problem is when the button is clicked, the Stacker class will open but only a frame without any components. When I switch the main method into the Stacker class, the program works fine. What is the problem?
Here is the code:
The Sampling class:
public class Sampling extends JFrame implements ActionListener
{
private JButton openStacker;
Stacker st;
public Sampling()
{
setSize(300,300);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new FlowLayout());
setLocationRelativeTo(null);
openStacker = new JButton("Start Stacker!");
add(openStacker);
openStacker.addActionListener(this);
setVisible(true);
}
public void actionPerformed(ActionEvent e)
{
dispose();
st = new Stacker();
}
public static void main (String args[])
{
new Sampling();
}
}
The Stacker game class:
public class Stacker extends JFrame implements KeyListener
{
int iteration = 1;
double time = 200;
int last = 0;
int m = 10;
int n = 20;
JButton b[][];
int length[] = {5,5};
int layer = 19;
int deltax[] = {0,0};
boolean press = false;
boolean forward = true;
boolean start = true;
public Stacker()
{
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setSize(400,580);
this.setUndecorated(false);
this.setLocationRelativeTo(null);
b = new JButton [m][n];
setLayout(new GridLayout(n,m));
for (int y = 0;y<n;y++)
{
for (int x = 0;x<m;x++)
{
b[x][y] = new JButton(" ");
b[x][y].setBackground(Color.DARK_GRAY);
add(b[x][y]);
b[x][y].setEnabled(false);
}//end inner for
}
this.setFocusable(true);
this.pack();
this.addKeyListener(this);
this.setVisible(true);
go();
}
public void go()
{
int tmp = 0;
Component temporaryLostComponent = null;
do{
if (forward == true)
{
forward();
} else {
back();
}
if (deltax[1] == 10-length[1])
{
forward = false;
} else if (deltax[1] == 0)
{
forward = true;
}
draw();
try
{
Thread.sleep((long) time);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}while(press == false);
if (layer>12)
{
time= 150-(iteration*iteration*2-iteration);
} else
{
time = time - 2.2;
}
iteration++;
layer--;
press = false;
tmp = check();
length[0] = length[1];
length[1] = tmp;
if (layer == -1)
{
JOptionPane.showMessageDialog(temporaryLostComponent, "Congratulations! You beat the game!");
repeat();
}
if (length[1] <= 0)
{
JOptionPane.showMessageDialog(temporaryLostComponent, "Game over! You reached line "+(18-layer)+"!");
repeat();
}
last = deltax[1];
start = false;
go();
}
public int check()
{
if (start == true)
{
return length[1];
}
else if (last<deltax[1])
{
if (deltax[1]+length[1]-1 <= last+length[0]-1)
{
return length[1];
}
else
{
return length[1]-Math.abs((deltax[1]+length[1])-(last+length[0]));
}
}
else if (last>deltax[1])
{
return length[1]-Math.abs(deltax[1]-last);
}
else
{
return length[1];
}
}
public void forward()
{
deltax[0] = deltax[1];
deltax[1]++;
}
public void back()
{
deltax[0] = deltax[1];
deltax[1]--;
}
public void draw()
{
for (int x = 0;x<length[1];x++)
{
b[x+deltax[0]][layer].setBackground(Color.DARK_GRAY);
}
for (int x = 0;x<length[1];x++)
{
b[x+deltax[1]][layer].setBackground(Color.CYAN);
}
}
public void repeat()
{
if(JOptionPane.showConfirmDialog(null, "PLAY AGAIN?","WARNING",JOptionPane.YES_NO_OPTION)== JOptionPane.YES_OPTION)
{
dispose();
new Stacker();
}else{
System.exit(0);
}
}
public void keyPressed(KeyEvent e)
{
if (e.getKeyCode() == KeyEvent.VK_SPACE)
{
press = true;
}
}
public void keyReleased(KeyEvent arg0)
{
}
public void keyTyped(KeyEvent arg0)
{
}
}
Just to put all my comments into an answer, and give you somewhere to start with:
Comment 1:
Take out go(); see that happens. I tested it and it will work. If you leave it there, even the frame's close button is jammed. You're blocking the edt with the while->Thread.sleep junk. You'll want to do some refactoring. You're code it hard to follow and I have no idea what you're trying to do, so I didn't even attempt it
Comment 2:
If you're wondering why it works when you just run the main from the Stacker class, it's probably because you are running it outside the EDT,
public static void main(String[] args) { new Stacker(); }. What happens when you click the button, that action is performed within the EDT, and hence your new Stacker() will be run on the EDT. In which case the EDT gets blocked by your while loop. If you try run the program from the Stacker class, but wrap it in a SwingUtilities.invokeLater, you will also notice the program fails to work. Swing programs should be run on the EDT though.
Comment 2: Read the first few sections on Concurrency with Swing
So what you can do is use a Swing Timer (which operates on the EDT) for the game loop. What I did was refactor your code a bit. It doesn't operate the way you want it to yet, only because I didn't really understand the logic of your code. So I couldn't get it to work. What I did though, is put some of the logic into the Timer.
Timer timer = new Timer((int)time, new ActionListener(){
public void actionPerformed(ActionEvent event) {
if (forward == true) {
forward();
} else {
back();
}
if (deltax[1] == 10 - length[1]) {
forward = false;
} else if (deltax[1] == 0) {
forward = true;
}
draw();
}
});
And when the go() method is called, it just starts the timer by calling timer.start(). Basically what you need to know about the timer, is that every tick (the milliseconds you pass it), the actionPerformed will be called. So you can update the game state in that method, just like you did in the while loop each iteration.
Take some time to go over How to Use Swing Timers
To get the game working properly, you still need to make some adjustments, but this should give you a head start.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Sampling extends JFrame implements ActionListener {
private JButton openStacker;
Stacker st;
public Sampling() {
setSize(300, 300);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new FlowLayout());
setLocationRelativeTo(null);
openStacker = new JButton("Start Stacker!");
add(openStacker);
openStacker.addActionListener(this);
setVisible(true);
}
public void actionPerformed(ActionEvent e) {
dispose();
st = new Stacker();
}
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable(){
public void run() {
new Sampling();
}
});
}
}
class Stacker extends JFrame implements KeyListener {
int iteration = 1;
double time = 200;
int last = 0;
int m = 10;
int n = 20;
JButton b[][];
int length[] = {5, 5};
int layer = 19;
int deltax[] = {0, 0};
boolean press = false;
boolean forward = true;
boolean start = true;
Timer timer = new Timer((int)time, new ActionListener(){
public void actionPerformed(ActionEvent event) {
if (forward == true) {
forward();
} else {
back();
}
if (deltax[1] == 10 - length[1]) {
forward = false;
} else if (deltax[1] == 0) {
forward = true;
}
draw();
}
});
public Stacker() {
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setSize(400, 580);
this.setUndecorated(false);
this.setLocationRelativeTo(null);
b = new JButton[m][n];
setLayout(new GridLayout(n, m));
for (int y = 0; y < n; y++) {
for (int x = 0; x < m; x++) {
b[x][y] = new JButton(" ");
b[x][y].setBackground(Color.DARK_GRAY);
add(b[x][y]);
b[x][y].setEnabled(false);
}//end inner for
}
this.setFocusable(true);
this.pack();
JPanel panel = (JPanel)getContentPane();
panel.addKeyListener(this);
this.setVisible(true);
panel.requestFocusInWindow();
go();
}
public void go() {
int tmp = 0;
Component temporaryLostComponent = null;
timer.start();
if (layer > 12) {
time = 150 - (iteration * iteration * 2 - iteration);
} else {
time = time - 2.2;
}
iteration++;
layer--;
press = false;
tmp = check();
length[0] = length[1];
length[1] = tmp;
if (layer == -1) {
JOptionPane.showMessageDialog(temporaryLostComponent, "Congratulations! You beat the game!");
repeat();
}
if (length[1] <= 0) {
JOptionPane.showMessageDialog(temporaryLostComponent, "Game over! You reached line " + (18 - layer) + "!");
repeat();
}
last = deltax[1];
start = false;
//go();
}
public int check() {
if (start == true) {
return length[1];
} else if (last < deltax[1]) {
if (deltax[1] + length[1] - 1 <= last + length[0] - 1) {
return length[1];
} else {
return length[1] - Math.abs((deltax[1] + length[1]) - (last + length[0]));
}
} else if (last > deltax[1]) {
return length[1] - Math.abs(deltax[1] - last);
} else {
return length[1];
}
}
public void forward() {
deltax[0] = deltax[1];
deltax[1]++;
}
public void back() {
deltax[0] = deltax[1];
deltax[1]--;
}
public void draw() {
for (int x = 0; x < length[1]; x++) {
b[x + deltax[0]][layer].setBackground(Color.DARK_GRAY);
}
for (int x = 0; x < length[1]; x++) {
b[x + deltax[1]][layer].setBackground(Color.CYAN);
}
}
public void repeat() {
if (JOptionPane.showConfirmDialog(null, "PLAY AGAIN?", "WARNING", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
dispose();
new Stacker();
} else {
System.exit(0);
}
}
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
System.out.println("Pressed");
press = true;
}
}
public void keyReleased(KeyEvent arg0) {
}
public void keyTyped(KeyEvent arg0) {
}
}
Notice the SwingUtilities.invokeLater in the main. That's how you can start up the program on the EDT. The link on Concurrency In Swing will give you more information.
I am working on a project involving JSwing.
The project consists of a JLayeredPane that contains a JScrollPane and a game board (JPanel with JPanels with JLabel).
My problem is that I added a custom mouse listener to the JLayeredPane. However, when I click on the JScrollPane area the listener doesn't seem to register. However, if I click other places, it does.
Can anyone explain why this is occuring?
Thanks in advance!
Code to create panels + add listener to JLayeredPane:
private void initPanels() {
JLayeredPane layeredPane = new JLayeredPane();
layeredPane.setLayout(new BorderLayout());
layeredPane.add(makeBoardPanel(), BorderLayout.CENTER, JLayeredPane.DEFAULT_LAYER);
JPanel panel = new JPanel(new BorderLayout());
panel.add(makeButtonPanel(), BorderLayout.WEST);
panel.add(makeDeckPanel(), BorderLayout.EAST);
layeredPane.add(panel, BorderLayout.SOUTH, JLayeredPane.DEFAULT_LAYER);
layeredPane.add(makeMeeplePanel(), BorderLayout.NORTH, JLayeredPane.DEFAULT_LAYER);
//TODO: fix adapter + add others;
TileMouseAdapter tileMouseAdapter = new TileMouseAdapter(layeredPane);
layeredPane.addMouseListener(tileMouseAdapter);
layeredPane.addMouseMotionListener(tileMouseAdapter);
add(layeredPane);
myController.reset(TileDeck.getDemo(), NUM_PLAYERS); //sets up game
}
Code to make Board with JScrollPane:
private JScrollPane makeBoardPanel() {
JScrollPane scrollPane = new JScrollPane(CarcassoneBoard.getInstance());
CarcassoneBoard.getInstance().setScrollPane(scrollPane);
return scrollPane;
}
Here is the code for that last line (myController.reset( ... ))
public void reset(Map<TileFactory, Integer> deck, int numPlayers) {
myDeck.reset(deck);
myBoard.reset(myDeck.getDeckSize()); //only panel contained in JScrollPane
myMeeplePanel.reset(numPlayers);
}
Reset method for myBoard ...
public void reset(int num_tiles) {
super.removeAll();
NUM_TILES = num_tiles;
BOARD_SIZE = (int) Math.ceil(num_tiles / 2.0);
setLayout(new GridLayout(BOARD_SIZE, BOARD_SIZE, 1, 1));
setPreferredSize(new Dimension(BOARD_SIZE * TILE_WIDTH, BOARD_SIZE * TILE_HEIGHT));
STARTING_TILE = new BubbleCity((int) Math.ceil(BOARD_SIZE / 2.0 - 1), (int) Math.ceil(BOARD_SIZE / 2.0 - 1));
myTiles = new TilePanel[BOARD_SIZE][BOARD_SIZE];
for (int i = 0; i < BOARD_SIZE; i++) {
for (int j = 0; j < BOARD_SIZE; j++) {
myTiles[i][j] = new TilePanel(i, j, new EmptyTile(i, j));
add(myTiles[i][j]);
}
}
myTiles[STARTING_TILE.getRow()][STARTING_TILE.getCol()].setTile(STARTING_TILE);
revalidate();
repaint();
}
Here is the code for the mouse listener:
public class TileMouseAdapter extends MouseAdapter {
private JLayeredPane myLayeredPane;
private Tile myTile;
private TilePanel myClickedPanel;
public TileMouseAdapter(JLayeredPane layeredPane) {
myLayeredPane = layeredPane;
}
private void reset() {
if (myTile != null) {
myLayeredPane.remove(myTile);
myLayeredPane.revalidate();
myLayeredPane.repaint();
}
myTile = null;
myClickedPanel = null;
}
//point p is a point relative to myLayeredPane...
public Point translatePoint(Component component, Point p) {
return SwingUtilities.convertPoint(myLayeredPane, p, component);
}
//point p is relative to the screen (event.getLocationOnScree()). We want to check if that point is in component.
public boolean containsPoint(Component component, Point p) {
Point translated = translatePoint(component, p );
return translated.getX() > 0 && translated.getY() > 0
&& translated.getX() < component.getWidth() && translated.getY() < component.getHeight();
}
#Override
public void mouseClicked(MouseEvent event) {
}
#Override
public void mousePressed(MouseEvent event) {
//no registering jscrollpane?
CarcassoneBoard board = CarcassoneBoard.getInstance();
TileDeck deck = TileDeck.getInstance();
System.out.println("pressed!");
if (containsPoint(deck, event.getPoint())) {
System.out.println("deck");
myClickedPanel = deck;
myTile = myClickedPanel.getTile();
}
else if (containsPoint(board.getScrollPane().getViewport(), event.getPoint())) {
System.out.println("board");
Component component = board.getComponentAt(translatePoint(board.getScrollPane().getViewport(), event.getPoint()));
if (component instanceof CarcassoneBoard) {return;} //user clicked in between tiles...do nothing.
myClickedPanel = (TilePanel) component;
myTile = myClickedPanel.getTile();
}
if (myTile == null || myTile instanceof EmptyTile || !myTile.isDraggable()) {
//TODO: scroll with drag!
reset();
return;
}
myClickedPanel.setEmpty(); //panel will set an empty tile automatically and remove myTile...
int x = event.getPoint().x - myTile.getWidth() / 2;
int y = event.getPoint().y - myTile.getHeight() / 2;
myTile.setLocation(x, y);
myLayeredPane.revalidate();
myLayeredPane.repaint();
try {
myLayeredPane.add(myTile, JLayeredPane.DRAG_LAYER);
myLayeredPane.revalidate();
myLayeredPane.repaint();
} catch (IllegalArgumentException e) {
//TODO: deal with this?
//gives error for some unknown reason, but doesnt effect anything? ignore...dumb error cus jswing sucks
}
}
#Override
public void mouseDragged(MouseEvent event) {
if (myTile == null) {
return;
}
int x = event.getPoint().x - myTile.getWidth() / 2;
int y = event.getPoint().y - myTile.getHeight() / 2;
myTile.setLocation(x, y);
myLayeredPane.revalidate();
myLayeredPane.repaint();
}
#Override
public void mouseReleased(MouseEvent event) {
if (myTile == null) {// || !myTile.isDraggable()) {
return; //do nothing...board scrolling
}
CarcassoneBoard board = CarcassoneBoard.getInstance();
myLayeredPane.remove(myTile);
TilePanel dropped = (TilePanel) board.getComponentAt(translatePoint(board.getScrollPane().getViewport(), event.getPoint()));
if (dropped == null) {
//reset tile to original spot
myClickedPanel.setTile(myTile);
reset();
return;
}
dropped.setTile(myTile);
reset();
}
}
NOTE:
I changed makeBoardPanel() to this:
private JPanel makeBoardPanel() {
return CarcassoneBoard.getInstance();
}
and the listener seemed to register clicks on the board which is what led me to believe
that it has something to do with the JScrollPane. I determined this by putting a print statement right after the mousePressed method header. When I clicked on the board it printed the statement right before complaining about the fact that I was referencing a JScrollPane that didn't exist later in the code :/
Been working with JTabbedPane and trying to customize it when using SCROLL_TAB_LAYOUT specifically with the scroll direction buttons.
I'm extending BasicTabbedPaneUI, but I don't see a method or ability to change the location of the scroll buttons. Searched around and don't see any one doing this other than just using different look and feels. Control over the location of scroll direction buttons would be very useful in general I feel.
Does anyone have any ideas how to do this while extending BasicTabbedPaneUI or any other method?
I'm assuming you want to move the scroll backwards button to the other side of the tabs.
In the BasicTabbedPaneUI class, there's a createDecreaseButton method that's package only (no access modifier).
It appears you're going to have to create your own BasicTabbedPaneUI class, with your own version of createDecreaseButton.
I changed the scroll button position without using many other components but simply extends the BasicTabbedPaneUI and hacking its paint() method to adjust the button position.
Here is the code:
public class MyTabbedScrollPane extends JTabbedPane {
public MyTabbedScrollPane ()
{
super ();
}
public MyTabbedScrollPane ( final int tabPlacement )
{
super ( tabPlacement );
}
public MyTabbedScrollPane ( final int tabPlacement, final int tabLayoutPolicy )
{
super ( tabPlacement, tabLayoutPolicy );
initialize();
}
public void initialize() {
setUI(new MyTabbedPaneUI());
}
private class MyTabbedPaneUI extends BasicTabbedPaneUI {
private int leadingTabIndex;
private Point tabViewPosition;
private Component adjustedButton;
private boolean scrollableTabLayoutEnabled() {
return tabPane.getTabLayoutPolicy() == SCROLL_TAB_LAYOUT;
}
/*
* Target button and view port utilities
*/
private Component findBackwardButton() {
Component[] comps = tabPane.getComponents();
for(Component comp:comps) {
if(comp instanceof BasicArrowButton) {
int direction = ((BasicArrowButton)comp).getDirection();
if(tabPane.getTabPlacement() == TOP || tabPane.getTabPlacement() == BOTTOM) {
if(direction == WEST) {
return comp;
}
}
}
}
return null;
}
private JViewport findViewPort() {
Component[] comps = tabPane.getComponents();
for(Component comp:comps) {
if(comp instanceof JViewport) {
return (JViewport)comp;
}
}
return null;
}
/*
* Override View port controlling (copy from BasicTabbedPaneUI.java)
*/
public void scrollForward(int tabPlacement) {
JViewport viewport = findViewPort();
Dimension viewSize = viewport.getViewSize();
Rectangle viewRect = viewport.getViewRect();
if (tabPlacement == TOP || tabPlacement == BOTTOM) {
if (viewRect.width >= viewSize.width - viewRect.x) {
return; // no room left to scroll
}
} else { // tabPlacement == LEFT || tabPlacement == RIGHT
if (viewRect.height >= viewSize.height - viewRect.y) {
return;
}
}
setLeadingTabIndex(tabPlacement, leadingTabIndex+1);
}
public void scrollBackward(int tabPlacement) {
if (leadingTabIndex == 0) {
return; // no room left to scroll
}
setLeadingTabIndex(tabPlacement, leadingTabIndex-1);
}
public void setLeadingTabIndex(int tabPlacement, int index) {
JViewport viewport = findViewPort();
leadingTabIndex = index;
Dimension viewSize = viewport.getViewSize();
Rectangle viewRect = viewport.getViewRect();
int offsetX = adjustedButton.getWidth()+2;
switch(tabPlacement) {
case TOP:
case BOTTOM:
tabViewPosition.x = leadingTabIndex == 0? 0-offsetX : rects[leadingTabIndex].x-offsetX;
if ((viewSize.width - tabViewPosition.x) < viewRect.width) {
// We've scrolled to the end, so adjust the viewport size
// to ensure the view position remains aligned on a tab boundary
Dimension extentSize = new Dimension(viewSize.width - tabViewPosition.x,
viewRect.height);
viewport.setExtentSize(extentSize);
}
break;
case LEFT:
case RIGHT:
tabViewPosition.y = leadingTabIndex == 0? 0 : rects[leadingTabIndex].y;
if ((viewSize.height - tabViewPosition.y) < viewRect.height) {
// We've scrolled to the end, so adjust the viewport size
// to ensure the view position remains aligned on a tab boundary
Dimension extentSize = new Dimension(viewRect.width,
viewSize.height - tabViewPosition.y);
viewport.setExtentSize(extentSize);
}
}
viewport.setViewPosition(tabViewPosition);
}
/*
* UI Rendering
*/
public void paint(final Graphics g, JComponent c) {
super.paint(g, c);
if(scrollableTabLayoutEnabled()) {
if(adjustedButton == null) {
adjustedButton = findBackwardButton();
tabViewPosition = new Point(0-(adjustedButton.getWidth()+2), 0);
Component[] comps = tabPane.getComponents();
for(Component comp:comps) {
if(comp instanceof BasicArrowButton) {
if(comp instanceof BasicArrowButton) {
BasicArrowButton button = (BasicArrowButton)comp;
int direction = button.getDirection();
if(tabPane.getTabPlacement() == TOP || tabPane.getTabPlacement() == BOTTOM) {
// left align the west button
if(direction == WEST) {
button.removeActionListener(button.getActionListeners()[0]);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
scrollBackward(tabPane.getTabPlacement());
}
});
} else if(direction == EAST) {
button.removeActionListener(button.getActionListeners()[0]);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
scrollForward(tabPane.getTabPlacement());
}
});
}
}
}
}
}
}
if(adjustedButton != null && adjustedButton.isVisible()) {
// move the scroll button
int by = adjustedButton.getY();
adjustedButton.setLocation(0, by);
findViewPort().setViewPosition(tabViewPosition);
return;
}
}
}
}
}
I would like to ask the same thing than this question but using SWT: Is there a way to make a Button with your own button graphic not just with an image inside the button? If not is another way to create a custom button in java?
public class ImageButton extends Canvas {
private int mouse = 0;
private boolean hit = false;
public ImageButton(Composite parent, int style) {
super(parent, style);
this.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
switch (mouse) {
case 0:
// Default state
e.gc.drawString("Normal", 5, 5);
break;
case 1:
// Mouse over
e.gc.drawString("Mouse over", 5, 5);
break;
case 2:
// Mouse down
e.gc.drawString("Hit", 5, 5);
break;
}
}
});
this.addMouseMoveListener(new MouseMoveListener() {
public void mouseMove(MouseEvent e) {
if (!hit)
return;
mouse = 2;
if (e.x < 0 || e.y < 0 || e.x > getBounds().width
|| e.y > getBounds().height) {
mouse = 0;
}
redraw();
}
});
this.addMouseTrackListener(new MouseTrackAdapter() {
public void mouseEnter(MouseEvent e) {
mouse = 1;
redraw();
}
public void mouseExit(MouseEvent e) {
mouse = 0;
redraw();
}
});
this.addMouseListener(new MouseAdapter() {
public void mouseDown(MouseEvent e) {
hit = true;
mouse = 2;
redraw();
}
public void mouseUp(MouseEvent e) {
hit = false;
mouse = 1;
if (e.x < 0 || e.y < 0 || e.x > getBounds().width
|| e.y > getBounds().height) {
mouse = 0;
}
redraw();
if (mouse == 1)
notifyListeners(SWT.Selection, new Event());
}
});
this.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if (e.keyCode == '\r' || e.character == ' ') {
Event event = new Event();
notifyListeners(SWT.Selection, event);
}
}
});
}
}
No, you can add a PaintListener to a button, but it will probably look really strange.
What you would need to do is to set the style of the window to "owner drawn" and than add your drawing code in the Button#wmDrawChild method. This means you need to add dependencies on internal SWT-classes and it will only work for Windows.