JPanel doesn't get new values (anymore) - java

So, I'm trying to program a Game of Life simulation (Conway), and I want to show it in a JFrame.
For this purpose, I've created a JPanel, and it works perfectly, until I try to actually show a new generation. With prints, I've figured out, that the list is actually correct inside the newGeneration() method, but when paint(Graphics g) gets called (aka, when I try to repaint the JFrame), the list isn't updating.
I'm sure I've missed something obvious, and I'm not well versed in Java, but it's just getting so annoying. I'd really appreciate your help.
Here's my code;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class Main {
public static void main(String[] args) {
new GameOfLife();
}
}
class GameOfLife {
// Initialising all class wide variables; sorted by type
JFrame frame = new JFrame("Game of Life");
JPanel panel;
Scanner gameSize = new Scanner(System.in);
String dimensions;
String splitHorizontal;
String splitVertical;
String confirmation;
Boolean accepted = false;
Integer split;
Integer horizontal;
Integer vertical;
Integer livingNeighbours;
int[][] cells;
int[][] newCells;
public GameOfLife() {
// Prompt for game Size
System.out.println("Please enter your game size in the following format; 'Horizontal,Vertical'");
// Run until viable game Size has been chosen
while (!accepted) {
dimensions = gameSize.nextLine();
// Check for correct format
if (dimensions.contains(",")) {
split = dimensions.indexOf(",");
splitHorizontal = dimensions.substring(0, split);
splitVertical = dimensions.substring(split + 1);
// Check for validity of inputs
if (splitHorizontal.matches("[0-9]+") && splitVertical.matches("[0-9]+")) {
horizontal = Integer.parseInt(dimensions.substring(0, split));
vertical = Integer.parseInt(dimensions.substring(split + 1));
// Check for game Size
if (horizontal > 1000 || vertical > 1000) {
System.out.println("A game of this Size may take too long to load.");
} else {
// Confirmation Prompt
System.out.println("Your game will contain " + horizontal + " columns, and " + vertical + " rows, please confirm (Y/N)");
confirmation = gameSize.nextLine();
// Check for confirmation, anything invalid is ignored
if (confirmation.matches("Y")) {
accepted = true;
System.out.println("Thank you for your confirmation. Please select live cells. Once your happy with your game, press Spacebar to start the Simulation.");
// Setting parameters depending on Size
frame.setSize(horizontal * 25 + 17, vertical * 25 + 40);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
}
}
// Prompt asking for new dimensions in case of invalid dimensions or non confirmation
if (!accepted) {
System.out.println("Please enter different dimensions.");
}
}
// Creating list of cells
cells = new int[horizontal][vertical];
// Showing the empty panel for selection of live cells
panel = new PaintCells(horizontal, vertical, cells);
frame.add(panel);
// Select live cells
panel.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
if (cells[(int) Math.ceil(e.getX() / 25)][(int) Math.ceil(e.getY() / 25)] == 1) {
cells[(int) Math.ceil(e.getX() / 25)][(int) Math.ceil(e.getY() / 25)] = 0;
} else {
cells[(int) Math.ceil(e.getX() / 25)][(int) Math.ceil(e.getY() / 25)] = 1;
}
frame.repaint();
}
});
// Simulation start
frame.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
if (e.getKeyChar() == ' ') {
newGeneration();
}
}
#Override
public void keyPressed(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
}
});
}
// Generating new generations
void newGeneration() {
newCells = new int[horizontal][vertical];
// Pause inbetween generations
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
/*
* Way of Life Rules:
* Living cells with 2 or 3 living neighbours live on to the next generation.
* Dead cells with exactly 3 living neighbours become living cells in the next generation.
* Every other living cell dies.
*/
// iterate through every cell
for (int l = 0; l < vertical; l++) {
for (int k = 0; k < horizontal; k++) {
livingNeighbours = 0;
// check amount of neighbours
if (k - 1 > -1) {
if (l - 1 > -1) {
if (cells[k - 1][l - 1] == 1) {
livingNeighbours++;
}
}
if (l + 1 < vertical) {
if (cells[k - 1][l + 1] == 1) {
livingNeighbours++;
}
}
if (cells[k - 1][l] == 1) {
livingNeighbours++;
}
}
if (k + 1 < horizontal) {
if (l - 1 >= 0) {
if (cells[k + 1][l - 1] == 1) {
livingNeighbours++;
}
}
if (l + 1 < vertical) {
if (cells[k + 1][l + 1] == 1) {
livingNeighbours++;
}
}
if (cells[k + 1][l] == 1) {
livingNeighbours++;
}
}
if (l - 1 >= 0) {
if (cells[k][l - 1] == 1) {
livingNeighbours++;
}
}
if (l + 1 < vertical) {
if (cells[k][l + 1] == 1) {
livingNeighbours++;
}
}
// change cell value depending on amount of neighbours
if (cells[k][l] == 1) {
if (livingNeighbours < 2 || livingNeighbours > 3) {
newCells[k][l] = 0;
} else {
newCells[k][l] = 1;
}
} else {
if (livingNeighbours == 3) {
newCells[k][l] = 1;
}
}
}
}
cells = newCells;
frame.validate();
frame.paint(frame.getGraphics());
newGeneration();
}
}
// Our canvas
class PaintCells extends JPanel {
private Integer horizontal;
private Integer vertical;
private int[][] newOriginalCells;
// Get our X and Y from the original prompts
public PaintCells(Integer originalHorizontal, Integer originalVertical, int[][] originalCells) {
this.horizontal = originalHorizontal;
this.vertical = originalVertical;
this.newOriginalCells = originalCells;
}
#Override
public void paint(Graphics g) {
for (int i = 0; i < vertical; i++) {
for (int j = 0; j < horizontal; j++) {
// Check cell value
if (newOriginalCells[j][i] == 1) {
g.setColor(Color.black);
} else {
g.setColor(Color.white);
}
// paint according to value
g.fillRect(j * 25, i * 25, 25, 25);
if (newOriginalCells[j][i] == 1) {
g.setColor(Color.white);
} else {
g.setColor(Color.black);
} // maybe change style?
g.drawRect(j * 25, i * 25, 25, 25);
}
}
}
}
I'm guessing, the problem is somewhere in newGeneration(), but other than that, I really have no idea anymore.

You have a common problem which I had myself a few months ago.
Java Swing GUI system works in thread called Event Dispatch Thread (EDT). This thread handle events like mouse clicks, typing etc. and paint the components to the screen. You should use this thread not as your main thread, but as sub-thread which working only once a certain time/when event happens, and not let him run continuously. In your code, since the user choose the cell to live, this thread run non-stop (because you started the program inside a listener, which is part of the EDT), and your GUI stuck, because it's updating only at the end of the thread.
You can solve this by using javax.swing.Timer. Timer is an object that allows you do tasks once a while, and it is perfect to this problem.
Use code like this:
ActionListener actionListaner = new ActionListener(){
public void actionPerformed(ActionEvent e){
//Put here you ne genration repeating code
}
};
int delay = 1000;//You delay between generations in millis
Timer timer = new timer(delay, actionListener);
The code in the actionPerformed method will repeat every second (or any other time you want it to repeat), and every operation of the timer will recall EDT instead of let it run non-stop.

Related

Java: Fixing array "linking" to allow for resetting

Firstly, I know Lists are better in almost(if not all) every way. I have encountered a substantial bug in an encoder program that I am making. In this program, I have a button that resets the "wheels" responsible for encoding(One of the wheels rotates after every letter encoded). I have a final int[][] called wheelsOriginal that is supposed to store the original value of the int[][] called wheels. Both of these arrays are int[9][36]. I would like a way of making wheelsOriginal stay unchanged throughout the program instead of changing with wheels for some reason. Here is a good way to recreate the problem(Sorry for the lengthy intToChar and charToInt methods!):
Main class:
import java.awt.*;
import javax.swing.*;
public class mainClass {
public static void main(String[] args) {
JFrame frame = new JFrame("Encoder");
frame.setBackground(new Color(225,225,225));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Display d = new Display();
frame.add(d);
frame.pack();
frame.setResizable(false);
frame.setVisible(true);
}
}
Display class:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Display extends JPanel implements ActionListener {
static JButton button;
static JLabel letter;
static int currentKey = -10;
static int wheel = 0;
static int[][] wheels = {
{-3,10,-6,2,20,-7,22,5,23,4,6,-9,3,26,0,15,21,-2,13,14,12,1,17,11,-8,-5,18,8,24,9,25,7,19,16,-4,-1},
{9,22,14,12,18,-3,3,6,16,1,-7,25,24,19,-8,8,21,20,5,-6,-2,26,15,-9,23,10,11,0,-5,4,-4,2,17,-1,13,7},
{18,20,-9,15,12,-6,16,-4,-5,14,24,-7,-8,-3,-1,1,4,7,8,25,10,11,5,6,13,22,19,21,23,-2,3,26,17,9,0,2},
{-3,10,-6,2,20,-7,22,5,23,4,6,-9,3,26,0,15,21,-2,13,14,12,1,17,11,-8,-5,18,8,24,9,25,7,19,16,-4,-1},
{9,22,14,12,18,-3,3,6,16,1,-7,25,24,19,-8,8,21,20,5,-6,-2,26,15,-9,23,10,11,0,-5,4,-4,2,17,-1,13,7},
{25,18,5,8,7,-8,4,11,6,-7,26,21,-1,24,15,23,9,-6,-2,13,16,22,-5,10,17,3,1,-9,0,12,2,19,-4,14,20,-3},
{25,18,5,8,7,-8,4,11,6,-7,26,21,-1,24,15,23,9,-6,-2,13,16,22,-5,10,17,3,1,-9,0,12,2,19,-4,14,20,-3},
{25,18,5,8,7,-8,4,11,6,-7,26,21,-1,24,15,23,9,-6,-2,13,16,22,-5,10,17,3,1,-9,0,12,2,19,-4,14,20,-3},
{9,22,14,12,18,-3,3,6,16,1,-7,25,24,19,-8,8,21,20,5,-6,-2,26,15,-9,23,10,11,0,-5,4,-4,2,17,-1,13,7}
};
final static int[][] wheelsOriginal = wheels;
public Display() {
setPreferredSize(new Dimension(250,200));
setFocusable(true);
button = new JButton("Reset");
button.setPreferredSize(new Dimension(225,50));
button.setFont(new Font(button.getFont().getFontName(), button.getFont().getStyle(), 25));
letter = new JLabel(" ", SwingConstants.CENTER);
letter.setPreferredSize(new Dimension(225,100));
letter.setFont(new Font(letter.getFont().getFontName(), Font.BOLD, 125));
letter.setForeground(new Color(0,0,0));
addKeyListener(
new KeyListener() {
public void keyPressed(KeyEvent e) {
if(currentKey == -10 && e.getKeyCode() >= 65 && e.getKeyCode() <= 90) {
currentKey = e.getKeyCode() - 64;
letter.setText(encode() + "");
}
else if(currentKey == -10 && e.getKeyCode() >= 48 && e.getKeyCode() <= 57) {
currentKey = -1 * (e.getKeyCode() - 48);
letter.setText(encode() + "");
}
}
public void keyReleased(KeyEvent e) {
currentKey = -10;
letter.setText(" ");
}
public void keyTyped(KeyEvent e) {}
}
);
button.addActionListener(this);
add(button, TOP_ALIGNMENT);
add(letter);
}
public static char encode() {
int key = currentKey;
for(int i = 0; i < 9; i++) {
key = wheels[i][key + 9];
}
for(int i = 8; i >= 0; i--) {
key = wheels[i][key + 9];
}
rotate(wheels[wheel], isEven(wheel));
if(wheel < 8) {
wheel++;
}
else {
wheel = 0;
}
return((char) key);
}
public static int[] rotate(int[] wheel, boolean positive) {
int revolve;
if(positive) {
revolve = wheel[wheel.length - 1];
for(int i = wheel.length - 2; i > 0; i--) {
wheel[i + 1] = wheel[i];
}
wheel[0] = revolve;
}
else {
revolve = wheel[0];
for(int i = 1; i < wheel.length - 1; i++) {
wheel[i - 1] = wheel[i];
}
wheel[wheel.length - 1] = revolve;
}
return wheel;
}
public static boolean isEven(int num) {
return (num/2 == Math.abs(num/2));
}
public void actionPerformed(ActionEvent e) {
if(e.getSource().equals(button)) {
reset();
grabFocus();
}
}
public static void reset() {
for(int[] i : wheels) {
for(int x : i) {
System.out.print(x + " ");
}
System.out.println("");
}
System.out.println(" ");
for(int[] i : wheelsOriginal) {
for(int x : i) {
System.out.print(x + " ");
}
System.out.println("");
}
System.out.println(" ");
wheels = wheelsOriginal;
for(int[] i : wheels) {
for(int x : i) {
System.out.print(x + " ");
}
System.out.println("");
}
wheel = 0;
letter.setText(" ");
currentKey = ' ';
System.out.println("Pressed");
}
}
Whenever a key is pressed, the encoded letter appears in the window. Even pressing the same key over and over again will usually produce different letters. Pressing the reset button should reset the encoder so that pressing the letter 'A' three times should produce S, E, and Q in that order. I also have designed this so that whenever you press the reset button, three large bulks of numbers print in the console. These show the wheels array before reset, the wheelsOriginal array, and the product wheels array in that order. If you press keys and click reset several times, you will notice that wheelsOriginal changes with wheels. Please help...
Your problem is that you are creating wheelsOriginal as reference of wheels instead of copy. Thats why when you change wheels, wheelsOriginal changes as well.
final static int[][] wheelsOriginal = wheels;
Something like this loop can be used to create a copy of wheels
int[][] wheelsOriginal = new int[wheels.length][];
for( int i = 0; i < wheelsOriginal.length; i++ )
{
wheelsOriginal[i] = Arrays.copyOf( wheels[i], wheels[i].length );
}
Also, for your charToInt and IntToChar methods - you could use the fact that chars are numbers and a->z A->Z 0->9 are grouped together to shorten them significantly
I didn't test that - in case you decide to use something like this - think and test yourself
public int charToInt( char c )
{
if( c >= '0' && c <= '9' ) {
return '0' - c;
} else if( c >= 'A' && c <= 'Z' ) {
return c - 'A' + 1;
} else if( c >= 'a' && c <= 'z' ) {
return c - 'a' + 1;
} else {
return -10;
}
}
public char intToChar( int c )
{
if( c >= -9 && c <= 0 ){
return (char)('0' - c);
} else if( c >= 1 && c <= 26 ){
return (char)(c + 'A' - 1);
} else{
return ' ';
}
}

Main method in different class can't detect button press

I'm making a program for practice and it's basically simulating winning/losing Powerball tickets. Anyways, the main class is in launchPowerBall.java and the button that I am trying to detect when clicked is in PowerBallGUI.java. The button works by itself, however, the main in launchPowerBall.java isn't able to detect it even though I set up myself a few setters and getters. Any clue what alternative I can do instead? Because it seems as though even though I run it through a while(true) loop and I keep pressing the button, there seems to be no detection from the main method.
Here's the action listener for the JButton:
start.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// If the button is pressed, delete text field and replace with new content
setButtonPressed(true);
outputText.setText("");
outputText.setText(outText);
}
});
Here's what I'm trying to do for the main method:
public static void main(String[] args) throws InterruptedException {
// Build the GUI object
PowerBallGUI GUI = new PowerBallGUI();
GUI.buildGUI();
//GUI.setTextArea("banana");
// Build the PowerBall object
PowerBall roll = new PowerBall();
while(true) {
if (GUI.getJButton().getModel().isPressed()) {
System.out.println("TEST");
}
}
}
Here's the three files if you need to see my code:
1) https://gist.github.com/anonymous/e5950413470202cd1ac6d24e238ff693
2) https://gist.github.com/anonymous/773e4e4454a79c057da78eed038fade1
3) https://gist.github.com/anonymous/82335e634c1f84607d3021b4d683cc65
Powerball...
public class PowerBall {
private int[] numbers = {0, 0, 0, 0, 0};
private int[] lotteryNumbers = {0, 0, 0, 0, 0};
private int powerBall;
private int lotteryPowerBall;
private double balance;
private double winnings;
/*** Constructor Methods ***/
public PowerBall() {
powerBall = 0;
balance = 1000;
winnings = 0;
}
/*** Mutator Methods ***/
public void randomize() {
int i;
int highestNumber = 59;
int highestPowerball = 32;
int temp = 0;
for (i = 0; i < numbers.length; i++) {
// Choose a random number
temp = (int)(Math.random() * highestNumber);
numbers[i] = temp;
}
// Choose a random Powerball number
powerBall = (int)(Math.random() * highestPowerball);
// Choose the lottery numbers
for (i = 0; i < lotteryNumbers.length; i++) {
temp = (int)(Math.random() * highestNumber);
lotteryNumbers[i] = temp;
}
lotteryPowerBall = (int)(Math.random() * highestPowerball);
}
public void calculate() {
int matches = 0;
int powerballMatches = 0;
if (balance > 0) {
// Check to see if there are any matches between the two sets of numbers
for (int i = 0; i < numbers.length; i++) {
for (int j = 0; j < lotteryNumbers.length; j++) {
if (numbers[i] == lotteryNumbers[j]) {
matches++;
}
}
}
// Check to see if the two different powerball numbers match
if (powerBall == lotteryPowerBall) {
powerballMatches = 1;
}
// Calculate the balance/winnings if there were any matches
if (matches == 0 && powerballMatches == 0) {
balance = balance - 2;
winnings = winnings - 2;
} else if (matches == 0 && powerballMatches == 1) {
balance = balance + 4;
winnings = winnings + 4;
} else if (matches == 0 && powerballMatches == 0 || matches == 1 && powerballMatches == 0) {
balance = balance - 2;
winnings = winnings - 2;
} else if (matches == 2 && powerballMatches == 1) {
balance = balance + 7;
winnings = winnings + 7;
} else if (matches == 3 && powerballMatches == 0) {
balance = balance + 7;
winnings = winnings + 7;
} else if (matches == 3 && powerballMatches == 1) {
balance = balance + 100;
winnings = winnings + 100;
} else if (matches == 4 && powerballMatches == 0) {
balance = balance + 100;
winnings = winnings + 100;
} else if (matches == 4 && powerballMatches == 1) {
balance = balance + 50000;
winnings = winnings + 50000;
} else if (matches == 5 && powerballMatches == 0) {
balance = balance + 1000000;
winnings = winnings + 1000000;
} else if (matches == 5 && powerballMatches == 1) {
balance = balance + 10000000;
winnings = winnings + 10000000;
}
//System.out.println("There is currently " + matches + " number matches.");
//System.out.println("There is currently " + powerballMatches + " powerball number matches.\n");
} else {
System.out.println("YOU ARE BROKE!");
}
}
/*** Accessor/Observor Methods ***/
public void displayBalance() {
System.out.print("Your balance is at: $");
System.out.printf("%.2f", balance);
}
public void displayWinnings() {
System.out.print("\nYou have currently won: $");
System.out.printf("%.2f", winnings);
System.out.println("\n");
}
public String toString() {
StringBuilder builder = new StringBuilder();
StringBuilder builder2 = new StringBuilder();
if (numbers.length == lotteryNumbers.length) {
for (int i = 0; i < numbers.length; i++) {
if (i < numbers.length - 1) {
builder.append(numbers[i] + ", ");
builder2.append(lotteryNumbers[i] + ", ");
} else {
builder.append(numbers[i] + " + ");
builder2.append(lotteryNumbers[i] + " + ");
}
}
} else {
return "ERROR: Numbers max set of numbers doesn't match Lottery Numbers max set of numbers!\n";
}
return "Your set of numbers were: " + builder + Integer.toString(powerBall) +
"\nThe powerball numbers were: " + builder2 + lotteryPowerBall;
}
}
Launcher
public class launchPowerBall {
public static void main(String[] args) throws InterruptedException {
// Build the GUI object
PowerBallGUI GUI = new PowerBallGUI();
GUI.buildGUI();
//GUI.setTextArea("banana");
// Build the PowerBall object
PowerBall roll = new PowerBall();
while(true) {
if (GUI.getJButton().getModel().isPressed()) {
System.out.println("TEST");
}
}
//System.out.println("TOO LATE!");
//while (true) {
/*roll.randomize();
System.out.println(roll.toString());
roll.calculate();
roll.displayBalance();
roll.displayWinnings();
Thread.sleep(500); */
//}
}
}
GUI
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingConstants;
public class PowerBallGUI {
private JButton start = new JButton("Launch the Powerball!");
private boolean buttonPressed = false;
private String outText;
/*** Constructor Methods ***/
public PowerBallGUI() {
#SuppressWarnings("unused")
JTextArea outputText = new JTextArea("Press the 'Launch the Powerball' button to start!");
}
/*** Setters ***/
/*** Sets the JTextArea object ***/
public void setTextArea (String str) {
this.outText = str;
}
public void setButtonPressed (Boolean bool) {
this.buttonPressed = bool;
}
/*** Getters ***/
public Boolean getButtonPressed() {
return this.buttonPressed;
}
public JButton getJButton() {
return this.start;
}
/*** Builds the GUI ***/
public void buildGUI() {
// Create the Java Frame itself
JFrame frame = new JFrame("Can YOU win the Powerball? v1.0 (Programmed by: Josh Yang)");
// Sets the default close operation of the frame
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Sets the size of the frame
frame.setSize(800, 500);
// Sets the location of the frame
centerGUI(frame);
// Allows the frame to be seen
frame.setVisible(true);
// Disables frame resizing
//frame.setResizable(false);
// Create the Java Panel itself
JPanel panel = new JPanel();
JPanel contentPanel = new JPanel();
JPanel launchPanel = new JPanel();
// Sets the sides of the Java panels
panel.setSize(800, 150);
// Set the panel background color
panel.setBackground(Color.YELLOW);
contentPanel.setBackground(Color.YELLOW);
launchPanel.setBackground(Color.PINK);
// Add the panel onto the frame
frame.add(panel, "North");
frame.add(contentPanel, BorderLayout.CENTER);
frame.add(launchPanel, BorderLayout.SOUTH);
// Set top panel's preferred size dimensions
panel.setPreferredSize(new Dimension(800, 100));
// Adds components onto the panel
JLabel title = new JLabel("Can YOU win the lottery? v1.0", SwingConstants.CENTER);
title.setFont(new Font("Serif", Font.BOLD, 25));
panel.add(title);
String text = "Basically, we start you off at $1000 and buy tickets in increments of $2 until you win big (if you do, that is)!";
JLabel description = new JLabel();
description.setText(text);
description.setFont(new Font("Serif", Font.PLAIN, 16));
panel.add(description);
// ContentPanel area
JLabel cDescription = new JLabel("Output: ");
contentPanel.add(cDescription);
JTextArea outputText = new JTextArea(17, 60);
outputText.setBackground(Color.PINK);
outputText.setEditable(false);
outputText.setText("Press the 'Launch the Powerball' button to start!");
contentPanel.add(outputText);
start.setLocation(100, 100);
launchPanel.add(start);
// Add an action listener to the JButton
start.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// If the button is pressed, delete text field and replace with new content
setButtonPressed(true);
outputText.setText("");
outputText.setText(outText);
}
});
frame.revalidate();
//frame.pack();
}
/*** Centers the GUI based on screen size ***/
public static void centerGUI(Window frame) {
// Gets the size of the screen
Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize();
int x = (int) ((dimension.getWidth() - frame.getWidth()) / 2);
int y = (int) ((dimension.getHeight() - frame.getHeight()) / 2);
// Set the frame location based on x and y
frame.setLocation(x, y);
}
}
The simple solution is to take the functionality you're "trying" to execute in the main method and put in the ActionListener...
start.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// If the button is pressed, delete text field and replace with new content
PowerBall roll = new PowerBall();
roll.randomize();
System.out.println(roll.toString());
roll.calculate();
roll.displayBalance();
roll.displayWinnings();
setButtonPressed(true);
outputText.setText("");
outputText.setText(outText);
}
});
If you "really" want to do more work in the main, you will need to generate some kind of observer pattern which can notified when something occurs.
You could put one on Powerball so it could notify you of a status change or on the GUI which notify you that the start button was pressed

Designing a multi-thread matrix in Java

I have a matrix that implements John Conway's life simulator in which every cell represents either life or lack of it.
Every life cycle follows these rules:
Any live cell with fewer than two live neighbors dies, as if caused by under-population.
Any live cell with two or three live neighbors lives on to the next generation.
Any live cell with more than three live neighbors dies, as if by overcrowding.
Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.
Every cell will have a thread that will perform the changes by the rules listed above.
I have implemented these classes:
import java.util.Random;
public class LifeMatrix {
Cell[][] mat;
public Action currentAction = Action.WAIT_FOR_COMMAND;
public Action changeAction;
public enum Action {
CHECK_NEIGHBORS_STATE,
CHANGE_LIFE_STATE,
WAIT_FOR_COMMAND
}
// creates a life matrix with all cells alive or dead or random between dead or alive
public LifeMatrix(int length, int width) {
mat = new Cell[length][width];
for (int i = 0; i < length; i++) { // populate the matrix with cells randomly alive or dead
for (int j = 0; j < width; j++) {
mat[i][j] = new Cell(this, i, j, (new Random()).nextBoolean());
mat[i][j].start();
}
}
}
public boolean isValidMatrixAddress(int x, int y) {
return x >= 0 && x < mat.length && y >= 0 && y < mat[x].length;
}
public int getAliveNeighborsOf(int x, int y) {
return mat[x][y].getAliveNeighbors();
}
public String toString() {
String res = "";
for (int i = 0; i < mat.length; i++) { // populate the matrix with cells randomly alive or
// dead
for (int j = 0; j < mat[i].length; j++) {
res += (mat[i][j].getAlive() ? "+" : "-") + " ";
}
res += "\n";
}
return res;
}
public void changeAction(Action a) {
// TODO Auto-generated method stub
currentAction=a;
notifyAll(); //NOTIFY WHO??
}
}
/**
* Class Cell represents one cell in a life matrix
*/
public class Cell extends Thread {
private LifeMatrix ownerLifeMat; // the matrix owner of the cell
private boolean alive;
private int xCoordinate, yCoordinate;
public void run() {
boolean newAlive;
while (true) {
while (! (ownerLifeMat.currentAction==Action.CHECK_NEIGHBORS_STATE)){
synchronized (this) {//TODO to check if correct
try {
wait();
} catch (InterruptedException e) {
System.out.println("Interrupted while waiting to check neighbors");
}}
}
// now check neighbors
newAlive = decideNewLifeState();
// wait for all threads to finish checking their neighbors
while (! (ownerLifeMat.currentAction == Action.CHANGE_LIFE_STATE)) {
try {
wait();
} catch (InterruptedException e) {
System.out.println("Interrupted while waiting to change life state");
};
}
// all threads finished checking neighbors now change life state
alive = newAlive;
}
}
// checking the state of neighbors and
// returns true if next life state will be alive
// returns false if next life state will be dead
private boolean decideNewLifeState() {
if (alive == false && getAliveNeighbors() == 3)
return true; // birth
else if (alive
&& (getAliveNeighbors() == 0 || getAliveNeighbors() == 1)
|| getAliveNeighbors() >= 4)
return false; // death
else
return alive; // same state remains
}
public Cell(LifeMatrix matLifeOwner, int xCoordinate, int yCoordinate, boolean alive) {
this.ownerLifeMat = matLifeOwner;
this.xCoordinate = xCoordinate;
this.yCoordinate = yCoordinate;
this.alive = alive;
}
// copy constructor
public Cell(Cell c, LifeMatrix matOwner) {
this.ownerLifeMat = matOwner;
this.xCoordinate = c.xCoordinate;
this.yCoordinate = c.yCoordinate;
this.alive = c.alive;
}
public boolean getAlive() {
return alive;
}
public void setAlive(boolean alive) {
this.alive = alive;
}
public int getAliveNeighbors() { // returns number of alive neighbors the cell has
int res = 0;
if (ownerLifeMat.isValidMatrixAddress(xCoordinate - 1, yCoordinate - 1) && ownerLifeMat.mat[xCoordinate - 1][yCoordinate - 1].alive)
res++;
if (ownerLifeMat.isValidMatrixAddress(xCoordinate - 1, yCoordinate) && ownerLifeMat.mat[xCoordinate - 1][yCoordinate].alive)
res++;
if (ownerLifeMat.isValidMatrixAddress(xCoordinate - 1, yCoordinate + 1) && ownerLifeMat.mat[xCoordinate - 1][yCoordinate + 1].alive)
res++;
if (ownerLifeMat.isValidMatrixAddress(xCoordinate, yCoordinate - 1) && ownerLifeMat.mat[xCoordinate][yCoordinate - 1].alive)
res++;
if (ownerLifeMat.isValidMatrixAddress(xCoordinate, yCoordinate + 1) && ownerLifeMat.mat[xCoordinate][yCoordinate + 1].alive)
res++;
if (ownerLifeMat.isValidMatrixAddress(xCoordinate + 1, yCoordinate - 1) && ownerLifeMat.mat[xCoordinate + 1][yCoordinate - 1].alive)
res++;
if (ownerLifeMat.isValidMatrixAddress(xCoordinate + 1, yCoordinate) && ownerLifeMat.mat[xCoordinate + 1][yCoordinate].alive)
res++;
if (ownerLifeMat.isValidMatrixAddress(xCoordinate + 1, yCoordinate + 1) && ownerLifeMat.mat[xCoordinate + 1][yCoordinate + 1].alive)
res++;
return res;
}
}
public class LifeGameLaunch {
public static void main(String[] args) {
LifeMatrix lifeMat;
int width, length, populate, usersResponse;
boolean userWantsNewGame = true;
while (userWantsNewGame) {
userWantsNewGame = false; // in order to finish the program if user presses
// "No" and not "Cancel"
width = Integer.parseInt(JOptionPane.showInputDialog(
"Welcome to John Conway's life simulator! \n"
+ "Please enter WIDTH of the matrix:"));
length = Integer.parseInt(JOptionPane.showInputDialog(
"Welcome to John Conway's life simulator! \n"
+ "Please enter LENGTH of the matrix:"));
lifeMat = new LifeMatrix(length, width);
usersResponse = JOptionPane.showConfirmDialog(null, lifeMat + "\nNext cycle?");
while (usersResponse == JOptionPane.YES_OPTION) {
if (usersResponse == JOptionPane.YES_OPTION) {
lifeMat.changeAction(Action.CHECK_NEIGHBORS_STATE);
}
else if (usersResponse == JOptionPane.NO_OPTION) {
return;
}
// TODO leave only yes and cancel options
usersResponse = JOptionPane.showConfirmDialog(null, lifeMat + "\nNext cycle?");
}
if (usersResponse == JOptionPane.CANCEL_OPTION) {
userWantsNewGame = true;
}
}
}
}
My trouble is to synchronize the threads:
Every cell(a thread) must change its life/death state only after all threads have checked their neighbors. The user will invoke every next life cycle by clicking a button.
My logic, as can be understood from the run() method is to let every cell(thread) run and wait for the right action state that is represented by the variable currentAction in LifeMatrix class and go ahead and execute the needed action.
What I struggle with is how do I pass these messages to the threads to know when to wait and when execute next action?
Any suggestions to change the design of the program are very welcome as long as every cell will be implemented with a separate thread!
Using a CyclicBarrier should be easy to understand:
(updated to use 2 Barriers, and make use of inner class to make cell looks shorter and cleaner)
psuedo code:
public class LifeMatrix {
private CyclicBarrier cycleBarrier;
private CyclicBarrier cellUpdateBarrier;
//.....
public LifeMatrix(int length, int width) {
cycleBarrier = new CyclicBarrier(length * width + 1);
cellUpdateBarrier = new CyclicBarrier(length * width);
// follow logic of old constructor
}
public void changeAction(Action a) {
//....
cycleBarrier.await()
}
// inner class for cell
public class Cell implements Runnable {
// ....
#Override
public void run() {
while (...) {
cycleBarrier.await(); // wait until start of cycle
boolean isAlive = decideNewLifeState();
cellUpdateBarrier.await(); // wait until everyone completed
this.alive = isAlive;
}
}
}
}
I would solve this using two Phasers.
You use one Phaser to control cycles and one one to synchronize the cells when they determine if they are alive or not.
public class Cell extends Thread {
private LifeMatrix ownerLifeMat; // the matrix owner of the cell
private boolean alive;
private int xCoordinate, yCoordinate;
// Phaser that controls the cycles
private Phaser cyclePhaser;
// Phaser for cell synchronisation
private Phaser cellPhaser;
public Cell(LifeMatrix matLifeOwner, Phaser cyclePhaser, Phaser cellPhaser,
int xCoordinate, int yCoordinate, boolean alive) {
this.ownerLifeMat = matLifeOwner;
this.cyclePhaser = cyclePhaser;
this.cellPhaser = cellPhaser;
this.xCoordinate = xCoordinate;
this.yCoordinate = yCoordinate;
this.alive = alive;
// Register with the phasers
this.cyclePhaser.register();
this.cellPhaser.register();
}
public void run() {
boolean newAlive;
while (true) {
// Await the next cycle
cyclePhaser.arriveAndAwaitAdvance();
// now check neighbors
newAlive = decideNewLifeState();
// Wait until all cells have checked their state
cellPhaser.arriveAndAwaitAdvance();
// all threads finished checking neighbors now change life state
alive = newAlive;
}
}
// Other methods redacted
}
You control the cycles by having the main thread register on the cyclePhaser
and have it arrive on it to start the next cycle.

Updating JButton on a timer in a do-while loop

I'm having some trouble getting a JButton to update repeatedly (used with a timer) in a do-while loop. I'm working on a simple game, played on a 10 * 10 grid of tile objects which correspond to a JButton arrayList with 100 buttons.
This part of the program handles simple pathfinding (i.e. if I click on character, then an empty tile, the character will move through each tile on its way to the destination). There is a delay between each step so the user can see the character's progress.
In the current state of things, the movement is correct, but the JButton is only updated when the character reaches the destination, not on intermediate steps.
public void move(int terrainTile)
{
int currentPosition = actorList.get(selectedActor).getPosition();
int movementValue = 0;
int destination = terrainTile;
int destinationX = destination / 10;
int destinationY = destination % 10;
do
{
currentPosition = actorList.get(selectedActor).getPosition(); // Gets PC's current position (before move)
System.out.println("Old position is " + currentPosition);
int currentX = currentPosition / 10;
int currentY = currentPosition % 10;
if(actorList.get(selectedActor).getCurrentAP() > 0)
{
movementValue = 0;
if(destinationX > currentX)
{
movementValue += 10;
}
if(destinationX < currentX)
{
movementValue -= 10;
}
if(destinationY > currentY)
{
movementValue += 1;
}
if(destinationY < currentY)
{
movementValue -= 1;
}
int nextStep = currentPosition + movementValue;
myGame.setActorIdInTile(currentPosition, -1); //Changes ActorId in PC current tile back to -1
scrubTiles(currentPosition);
actorList.get(selectedActor).setPosition(nextStep); // Sets new position in actor object
System.out.println("Actor " + selectedActor + " " + actorList.get(selectedActor).getName() + " position has been updated to " + nextStep);
myGame.setActorIdInTile(nextStep, selectedActor); // Sets ActorId in moved to Tile
System.out.println("Tile " + nextStep + " actorId has been updated to " + selectedActor);
buttons.get(nextStep).setIcon(new ImageIcon(actorList.get(selectedActor).getImageName()));
// If orthagonal move AP-4
if(movementValue == 10 || movementValue == -10 || movementValue == 1 || movementValue == -1)
{
actorList.get(selectedActor).reduceAP(4);
}
// If diagonal move AP-6
else
{
actorList.get(selectedActor).reduceAP(6);
}
System.out.println(actorList.get(selectedActor).getName() + " has " + actorList.get(selectedActor).getCurrentAP() + " AP remaining");
try
{
Thread.sleep(500); // one second
}
catch (Exception e){}
buttons.get(nextStep).repaint();
}
else
{
System.out.println(actorList.get(selectedActor).getName() + " has insufficient AP to move");
break;
}
}while(destination != (currentPosition + movementValue));
What I've tried:
buttons.get(nextStep).repaint(); (Tried putting a command to repaint the button after setting the imageIcon. No change.
buttons.get(nextStep).revalidate(); (No 100% sure what this does - it came up as a potential solution, but doesn't work.
Steps 1 & 2 combined
Looked into the swing timer class - movement doesn't occur everytime an actionEvent is fired, (only if character is selected and target tile is empty) so not sure how I could get this to work
Any help would be greatly appreciated!
I really dont' know exactly what you wanted to know in your comments, though +1 to the answer above, seems to me that's the real cause. Have a look at this example program, simply add your call to the move(...) method inside the timerAction, seems like that can work for you. Here try this code :
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class GridExample
{
private static final int SIZE = 36;
private JButton[] buttons;
private int presentPos;
private int desiredPos;
private Timer timer;
private Icon infoIcon =
UIManager.getIcon("OptionPane.informationIcon");
private ActionListener timerAction = new ActionListener()
{
public void actionPerformed(ActionEvent ae)
{
buttons[presentPos].setIcon(null);
if (desiredPos < presentPos)
{
presentPos--;
buttons[presentPos].setIcon(infoIcon);
}
else if (desiredPos > presentPos)
{
presentPos++;
buttons[presentPos].setIcon(infoIcon);
}
else if (desiredPos == presentPos)
{
timer.stop();
buttons[presentPos].setIcon(infoIcon);
}
}
};
public GridExample()
{
buttons = new JButton[SIZE];
presentPos = 0;
desiredPos = 0;
}
private void createAndDisplayGUI()
{
JFrame frame = new JFrame("Grid Game");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel contentPane = new JPanel();
contentPane.setLayout(new GridLayout(6, 6, 5, 5));
for (int i = 0; i < SIZE; i++)
{
final int counter = i;
buttons[i] = new JButton();
buttons[i].setActionCommand("" + i);
buttons[i].addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent ae)
{
desiredPos = Integer.parseInt(
(String) buttons[counter].getActionCommand());
timer.start();
}
});
contentPane.add(buttons[i]);
}
buttons[presentPos].setIcon(infoIcon);
frame.setContentPane(contentPane);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
timer = new Timer(1000, timerAction);
}
public static void main(String... args)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
new GridExample().createAndDisplayGUI();
}
});
}
}
This is because you are doing your do { } while in the UI thread. To solve this, you should use a SwingWorker, or a javax.swing.Timer

Java exception handling with multiple classes

I need to make the following exceptions: NoSuchRowException if the row is not between 1 and 3, IllegalSticksException if the number of sticks taken is not between 1 and 3, and NotEnoughSticksException if the number of sticks taken is between 1 and 3, but more than the number of sticks remaining in that row. My issue is I really don't understand the syntax. If someone could help me get started with one exception, I think I can figure the others out.
So far I have the main class:
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package nimapp;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/**
*
* #author jrsullins
*/
public class NimApp extends JFrame implements ActionListener {
private static final int ROWS = 3;
private JTextField[] gameFields; // Where sticks for each row shown
private JTextField rowField; // Where player enters row to select
private JTextField sticksField; // Where player enters sticks to take
private JButton playButton; // Pressed to take sticks
private JButton AIButton; // Pressed to make AI's move
private NimGame nim;
public NimApp() {
// Build the fields for the game play
rowField = new JTextField(5);
sticksField = new JTextField(5);
playButton = new JButton("PLAYER");
AIButton = new JButton("COMPUTER");
playButton.addActionListener(this);
AIButton.addActionListener(this);
AIButton.setEnabled(false);
// Create the layout
JPanel mainPanel = new JPanel(new BorderLayout());
getContentPane().add(mainPanel);
JPanel sticksPanel = new JPanel(new GridLayout(3, 1));
mainPanel.add(sticksPanel, BorderLayout.EAST);
JPanel playPanel = new JPanel(new GridLayout(3, 2));
mainPanel.add(playPanel, BorderLayout.CENTER);
// Add the fields to the play panel
playPanel.add(new JLabel("Row: ", JLabel.RIGHT));
playPanel.add(rowField);
playPanel.add(new JLabel("Sticks: ", JLabel.RIGHT));
playPanel.add(sticksField);
playPanel.add(playButton);
playPanel.add(AIButton);
// Build the array of textfields to display the sticks
gameFields = new JTextField[ROWS];
for (int i = 0; i < ROWS; i++) {
gameFields[i] = new JTextField(10);
gameFields[i].setEditable(false);
sticksPanel.add(gameFields[i]);
}
setSize(350, 150);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
nim = new NimGame(new int[]{3, 5, 7});
draw();
}
// Utility function to redraw game
private void draw() {
for (int row = 0; row < ROWS; row++) {
String sticks = "";
for (int j = 0; j < nim.getRow(row); j++) {
sticks += "| ";
}
gameFields[row].setText(sticks);
}
rowField.setText("");
sticksField.setText("");
}
public void actionPerformed(ActionEvent e) {
// Player move
if (e.getSource() == playButton) {
// Get the row and number of sticks to take
int row = Integer.parseInt(rowField.getText())-1;
int sticks = Integer.parseInt(sticksField.getText());
// Play that move
nim.play(row, sticks);
// Redisplay the board and enable the AI button
draw();
playButton.setEnabled(false);
AIButton.setEnabled(true);
// Determine whether the game is over
if (nim.isOver()) {
JOptionPane.showMessageDialog(null, "You win!");
playButton.setEnabled(false);
}
}
// Computer move
if (e.getSource() == AIButton) {
// Determine computer move
nim.AIMove();
// Redraw board
draw();
AIButton.setEnabled(false);
playButton.setEnabled(true);
// Is the game over?
if (nim.isOver()) {
JOptionPane.showMessageDialog(null, "You win!");
playButton.setEnabled(false);
}
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
NimApp a = new NimApp();
}
}
The support class:
package nimapp;
import java.util.Random;
import javax.swing.JOptionPane;
import java.io.*;
import java.lang.*;
public class NimGame {
int x = 1;
int[] Sticks; //creating an array of sticks
int totalSticks = 0;
public NimGame(int[] initialSticks){
Sticks = initialSticks;}
public int getRow(int r){
return Sticks[r];}
public void play(int r, int s) throws IllegalSticksException {
try {
Sticks[r]=Sticks[r]-s;
if(s < 0 || s > 3)
throw new IllegalSticksException();
} catch (IllegalSticksException ex){
JOptionPane.showMessageDialog(null, "Not a valid row!");
} catch (IndexOutOfBoundsException e){
JOptionPane.showMessageDialog(null, "Too Many Sticks!");
}
}
public boolean isOver(){
int theTotal = 0;
for (int i = 0; i< Sticks.length; i++){
theTotal = Sticks[i];
System.out.println(Sticks[i]);
System.out.println(theTotal);
}
totalSticks = theTotal;
if (totalSticks <= 0){
return true;
}
else return false;
}
public void AIMove(){
Random randomInt = new Random ();
boolean tryRemove = true;
while(tryRemove && totalSticks >= 1){
int RandomRow = randomInt.nextInt(3);
if(Sticks[RandomRow] <= 0)//the computer can't remove from this row
continue;
//the max number to remove from row
int size = 3;
if( Sticks[RandomRow] < 3)//this row have least that 3 cards
size = Sticks[RandomRow];//make the max number to remove from the row be the number of cards on the row
int RandomDiscard = randomInt.nextInt(size) + 1;
Sticks[RandomRow] = Sticks[RandomRow] - RandomDiscard;
//I don't know if this is needed, but since we remove a RandomDiscard amount lest decrease the totalSticks
totalSticks = totalSticks - RandomDiscard;
//exit loop
tryRemove = false;
}
if(totalSticks <= 1){
int RandomRow = 0;
Sticks[RandomRow] = Sticks[RandomRow]-1;
isOver();
}
}
}
My issue is I really don't understand the syntax.
There is nothing wrong with the syntax as you have written it.
The problem is that you are catching the exception at the wrong place. You are (apparently) intending play to propagate the IllegalSticksException to its caller. But that won't happen because you are catching it within the play method.
There are two possible fixes depending on what you actually intent to happen.
You could remove the throws IllegalSticksException from the play signature.
You could remove the catch (IllegalSticksException ex){ ... } in play and catch/handle the exception at an outer level.

Categories