I have to do an assignment at school, it's a car park simulator, calling run(); from the main gives a perfectly normal simulation where the carspots get repainted every step but when calling it from an actionListener it only paints the result and not the steps inbetween. There are 6 classes down below where the main is in the Simulator class, and SimulatorView has the repaint(); in it.
So could anyone explain to me why it simulates every step when run() is called from the main, and why it just paints the output when run() is called from an ActionListener behind a JButton?
Simulator.java:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
public class Simulator implements ActionListener{
private CarQueue entranceCarQueue;
private CarQueue paymentCarQueue;
private CarQueue exitCarQueue;
private SimulatorView simulatorView;
private int day = 0;
private int hour = 0;
private int minute = 0;
private int tickPause = 100;
int weekDayArrivals= 50; // average number of arriving cars per hour
int weekendArrivals = 90; // average number of arriving cars per hour
int enterSpeed = 3; // number of cars that can enter per minute
int paymentSpeed = 10; // number of cars that can pay per minute
int exitSpeed = 9; // number of cars that can leave per minute
public Simulator() {
entranceCarQueue = new CarQueue();
paymentCarQueue = new CarQueue();
exitCarQueue = new CarQueue();
simulatorView = new SimulatorView(3, 6, 30, this);
}
public void run() {
for (int i = 0; i < 10000; i++) {
tick();
}
}
public void run(int steps) {
System.out.println("run " + steps + " steps");
for (int i = 0; i < steps; i++) {
tick();
}
}
private void tick() {
System.out.println("simulator-tick");
// Advance the time by one minute.
minute++;
while (minute > 59) {
minute -= 60;
hour++;
}
while (hour > 23) {
hour -= 24;
day++;
}
while (day > 6) {
day -= 7;
}
Random random = new Random();
// Get the average number of cars that arrive per hour.
int averageNumberOfCarsPerHour = day < 5
? weekDayArrivals
: weekendArrivals;
// Calculate the number of cars that arrive this minute.
double standardDeviation = averageNumberOfCarsPerHour * 0.1;
double numberOfCarsPerHour = averageNumberOfCarsPerHour + random.nextGaussian() * standardDeviation;
int numberOfCarsPerMinute = (int)Math.round(numberOfCarsPerHour / 60);
// Add the cars to the back of the queue.
for (int i = 0; i < numberOfCarsPerMinute; i++) {
Car car = new AdHocCar();
entranceCarQueue.addCar(car);
}
// Remove car from the front of the queue and assign to a parking space.
for (int i = 0; i < enterSpeed; i++) {
Car car = entranceCarQueue.removeCar();
if (car == null) {
break;
}
// Find a space for this car.
Location freeLocation = simulatorView.getFirstFreeLocation();
if (freeLocation != null) {
simulatorView.setCarAt(freeLocation, car);
int stayMinutes = (int) (15 + random.nextFloat() * 10 * 60);
car.setMinutesLeft(stayMinutes);
}
}
// Perform car park tick.
simulatorView.tick();
// Add leaving cars to the exit queue.
while (true) {
Car car = simulatorView.getFirstLeavingCar();
if (car == null) {
break;
}
car.setIsPaying(true);
paymentCarQueue.addCar(car);
}
// Let cars pay.
for (int i = 0; i < paymentSpeed; i++) {
Car car = paymentCarQueue.removeCar();
if (car == null) {
break;
}
// TODO Handle payment.
simulatorView.removeCarAt(car.getLocation());
exitCarQueue.addCar(car);
}
// Let cars leave.
for (int i = 0; i < exitSpeed; i++) {
Car car = exitCarQueue.removeCar();
if (car == null) {
break;
}
// Bye!
}
// Update the car park view.
simulatorView.updateView();
// Pause.
try {
Thread.sleep(tickPause);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
#Override
public void actionPerformed(ActionEvent e)
{
String command = e.getActionCommand();
switch (command) {
case "oneStep":
run(1);
break;
case "hundredSteps":
run(10);
break;
default:
break;
}
}
public static void main (String[] args) {
Simulator start = new Simulator();
}
}
SimulatorView.java:
import javax.swing.*;
import java.awt.*;
public class SimulatorView extends JFrame {
private CarParkView carParkView;
private int numberOfFloors;
private int numberOfRows;
private int numberOfPlaces;
private Car[][][] cars;
public SimulatorView(int numberOfFloors, int numberOfRows, int numberOfPlaces, Simulator parent) {
this.numberOfFloors = numberOfFloors;
this.numberOfRows = numberOfRows;
this.numberOfPlaces = numberOfPlaces;
cars = new Car[numberOfFloors][numberOfRows][numberOfPlaces];
carParkView = new CarParkView();
Container contentPane = getContentPane();
//contentPane.add(stepLabel, BorderLayout.NORTH);
contentPane.add(carParkView, BorderLayout.CENTER);
//contentPane.add(population, BorderLayout.SOUTH);
JButton stepForward = new JButton("oneStep");
stepForward.addActionListener(parent);
JButton stepHundredForward = new JButton("hundredSteps");
stepHundredForward.addActionListener(parent);
JMenuBar stepBar = new JMenuBar();
stepBar.add(stepForward);
stepBar.add(stepHundredForward);
contentPane.add(stepBar, BorderLayout.SOUTH);
pack();
setVisible(true);
updateView();
}
public void updateView() {
carParkView.updateView();
}
public int getNumberOfFloors() {
return numberOfFloors;
}
public int getNumberOfRows() {
return numberOfRows;
}
public int getNumberOfPlaces() {
return numberOfPlaces;
}
public Car getCarAt(Location location) {
if (!locationIsValid(location)) {
return null;
}
return cars[location.getFloor()][location.getRow()][location.getPlace()];
}
public boolean setCarAt(Location location, Car car) {
if (!locationIsValid(location)) {
return false;
}
Car oldCar = getCarAt(location);
if (oldCar == null) {
cars[location.getFloor()][location.getRow()][location.getPlace()] = car;
car.setLocation(location);
return true;
}
return false;
}
public Car removeCarAt(Location location) {
if (!locationIsValid(location)) {
return null;
}
Car car = getCarAt(location);
if (car == null) {
return null;
}
cars[location.getFloor()][location.getRow()][location.getPlace()] = null;
car.setLocation(null);
return car;
}
public Location getFirstFreeLocation() {
for (int floor = 0; floor < getNumberOfFloors(); floor++) {
for (int row = 0; row < getNumberOfRows(); row++) {
for (int place = 0; place < getNumberOfPlaces(); place++) {
Location location = new Location(floor, row, place);
if (getCarAt(location) == null) {
return location;
}
}
}
}
return null;
}
public Car getFirstLeavingCar() {
for (int floor = 0; floor < getNumberOfFloors(); floor++) {
for (int row = 0; row < getNumberOfRows(); row++) {
for (int place = 0; place < getNumberOfPlaces(); place++) {
Location location = new Location(floor, row, place);
Car car = getCarAt(location);
if (car != null && car.getMinutesLeft() <= 0 && !car.getIsPaying()) {
return car;
}
}
}
}
return null;
}
public void tick() {
System.out.println("simulatorview-tick");
for (int floor = 0; floor < getNumberOfFloors(); floor++) {
for (int row = 0; row < getNumberOfRows(); row++) {
for (int place = 0; place < getNumberOfPlaces(); place++) {
Location location = new Location(floor, row, place);
Car car = getCarAt(location);
if (car != null) {
car.tick();
}
}
}
}
}
private boolean locationIsValid(Location location) {
int floor = location.getFloor();
int row = location.getRow();
int place = location.getPlace();
if (floor < 0 || floor >= numberOfFloors || row < 0 || row > numberOfRows || place < 0 || place > numberOfPlaces) {
return false;
}
return true;
}
private class CarParkView extends JPanel {
private Dimension size;
private Image carParkImage;
/**
* Constructor for objects of class CarPark
*/
public CarParkView() {
size = new Dimension(0, 0);
}
/**
* Overridden. Tell the GUI manager how big we would like to be.
*/
public Dimension getPreferredSize() {
return new Dimension(800, 500);
}
/**
* Overriden. The car park view component needs to be redisplayed. Copy the
* internal image to screen.
*/
public void paintComponent(Graphics g) {
if (carParkImage == null) {
return;
}
Dimension currentSize = getSize();
if (size.equals(currentSize)) {
g.drawImage(carParkImage, 0, 0, null);
}
else {
// Rescale the previous image.
g.drawImage(carParkImage, 0, 0, currentSize.width, currentSize.height, null);
}
}
public void updateView() {
// Create a new car park image if the size has changed.
if (!size.equals(getSize())) {
size = getSize();
carParkImage = createImage(size.width, size.height);
}
Graphics graphics = carParkImage.getGraphics();
for(int floor = 0; floor < getNumberOfFloors(); floor++) {
for(int row = 0; row < getNumberOfRows(); row++) {
for(int place = 0; place < getNumberOfPlaces(); place++) {
Location location = new Location(floor, row, place);
Car car = getCarAt(location);
Color color = car == null ? Color.white : Color.red;
drawPlace(graphics, location, color);
}
}
}
repaint();
}
/**
* Paint a place on this car park view in a given color.
*/
private void drawPlace(Graphics graphics, Location location, Color color) {
graphics.setColor(color);
graphics.fillRect(
location.getFloor() * 260 + (1 + (int)Math.floor(location.getRow() * 0.5)) * 75 + (location.getRow() % 2) * 20,
60 + location.getPlace() * 10,
20 - 1,
10 - 1); // TODO use dynamic size or constants
}
}
}
Car.java:
public abstract class Car {
private
Location location;
private int minutesLeft;
private boolean isPaying;
/**
* Constructor for objects of class Car
*/
public Car() {
}
public Location getLocation() {
return location;
}
public void setLocation(Location location) {
this.location = location;
}
public int getMinutesLeft() {
return minutesLeft;
}
public void setMinutesLeft(int minutesLeft) {
this.minutesLeft = minutesLeft;
}
public boolean getIsPaying() {
return isPaying;
}
public void setIsPaying(boolean isPaying) {
this.isPaying = isPaying;
}
public void tick() {
System.out.println("car-tick");
minutesLeft--;
}
}
Location.java:
public class Location {
private int floor;
private int row;
private int place;
/**
* Constructor for objects of class Location
*/
public Location(int floor, int row, int place) {
this.floor = floor;
this.row = row;
this.place = place;
}
/**
* Implement content equality.
*/
public boolean equals(Object obj) {
if(obj instanceof Location) {
Location other = (Location) obj;
return floor == other.getFloor() && row == other.getRow() && place == other.getPlace();
}
else {
return false;
}
}
/**
* Return a string of the form floor,row,place.
* #return A string representation of the location.
*/
public String toString() {
return floor + "," + row + "," + place;
}
/**
* Use the 10 bits for each of the floor, row and place
* values. Except for very big car parks, this should give
* a unique hash code for each (floor, row, place) tupel.
* #return A hashcode for the location.
*/
public int hashCode() {
return (floor << 20) + (row << 10) + place;
}
/**
* #return The floor.
*/
public int getFloor() {
return floor;
}
/**
* #return The row.
*/
public int getRow() {
return row;
}
/**
* #return The place.
*/
public int getPlace() {
return place;
}
}
CarQueue.java:
import java.util.LinkedList;
import java.util.Queue;
public class CarQueue {
private Queue<Car> queue = new LinkedList<>();
public boolean addCar(Car car) {
return queue.add(car);
}
public Car removeCar() {
return queue.poll();
}
}
AdHocCar.java:
public class AdHocCar extends Car {
public AdHocCar() {
}
}
When you call run() from the actionListener, it is being executed on the EventDispatchTread, blocking that thread until the run() method terminates. Only after that point can the EDT act on the repaint request.
"In general Swing is not thread safe. All Swing components and related classes, unless otherwise documented, must be accessed on the event dispatching thread." see here
So when an action is performed, actionListener pick up that event and execute run() in the event dispatching thread.
At step 0, when it comes to repaint(), it doesn't draw anything because "The component will be repainted after all of the currently pending events have been dispatched." see here
The repaint() is waiting for the release of ActionListener.actionPerformed.
So it comes to step 1, the same thing happens. Only at the end, after the action event is released, the repaint() will be execute once. So that's why only results are there not steps in between.
My suggestion is put the painting logic in the main thread instead of the actionListener.
Related
I've had that project in my head for a few weeks so I made a game of life simulation program
but after comparing it to this simulation (https://playgameoflife.com/), none of what i've codded is accurate even tho I respected the rules in my code ...
is my code not correct ? what's wrong ?
(https://pastebin.com/SpMFQGJL)
public class GameOfLife {
/*
* STATIC PART - ENTRY POINT
*/
public static void main(String[] args) {
GameOfLife gol = new GameOfLife(new Dimension(480,270),4, true, new Random().nextInt(), new Random().nextInt(25)).init();
gol.start();
}
/*
* GameOfLife Class content
*/
/*
* GameGrid Vars (requiered for game grid)
* dimX = game of life grid size on X axis
* dimY = game of life grid size on Y axis
* cellScale = size of cells on X and Y axis on screen display
* fillperc = chances of a cell to be alive on grid generation
* borderTeleporter = is wall teleporter (on other side of map) !!useless at the moment!!
*/
public int dimX;
public int dimY;
public int cellScale;
public int fillperc;
public boolean borderTeleporter;
/*
* Generation vars
*/
public int currentSeed = 0;
public int generation = 0;
//Thread clock = the thread the game of life clock is on
public Thread clock;
/*
* frame = the frame the game of life grid is drawn on
* game_grid = component of game of life
*/
public JFrame frame;
public GameGrid game_grid;
/*
* GameOfLife
* #params args
*/
public GameOfLife(Dimension boardDimension, int cellScale, boolean borderTeleporter,int seed,int fillperc) {
if(seed != 0) currentSeed = seed;
this.dimX = (int) boardDimension.getWidth();
this.dimY = (int) boardDimension.getHeight();
this.cellScale = cellScale;
this.borderTeleporter = borderTeleporter;
this.fillperc = fillperc;
}
public GameOfLife init() {
if(currentSeed == 0) currentSeed = new Random().nextInt();
this.game_grid = new GameGrid(this).genBoard();
frame = new JFrame("Map: " + currentSeed + " Generation: " + generation);
frame.setUndecorated(true);
frame.addWindowListener(new WindowAdapter() {#Override public void windowClosing(WindowEvent e) {running = false;try{clock.join();}catch(Exception exx) {System.out.println("impossible de tuer le thread clock");};System.exit(0);}});
frame.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {}
#Override
public void keyReleased(KeyEvent e) {if(e.getKeyCode() == 32) {if(running)stop();else start();}else if(e.getKeyCode() == 116) {stop();fillperc = new Random().nextInt(25);currentSeed = new Random().nextInt();game_grid.genBoard();start();}}
#Override
public void keyPressed(KeyEvent e) {}
});
frame.setPreferredSize(new Dimension(dimX*cellScale,dimY*cellScale));
frame.pack();
frame.add(game_grid);
frame.pack();
frame.setVisible(true);
return this;
}
public boolean running = false;
public void start() {
running = true;
clock = new Thread(new Runnable() {
#Override
public void run() {
while(running) {
frame.setTitle("Map: " + currentSeed + " Generation: " + generation);
game_grid.calcBoard();
frame.repaint();
generation++;
try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}
}
}
});
clock.start();
}
public void stop() {
running = false;try{clock.join();}catch(Exception exx) {System.out.println("impossible de tuer le thread clock");}
}
}
and then the game grid class:
public class GameGrid extends JPanel{
/**
*
*/
private GameOfLife gol;
private BufferedImage img;
boolean first = true;
public GameGrid(GameOfLife gol) {
this.gol = gol;
}
public boolean[][] board;
public boolean[][] new_board;
public GameGrid genBoard() {
board = new boolean[gol.dimX][gol.dimY];
Random seed = new Random(gol.currentSeed);
for(int x = 0; x < gol.dimX; x++) {
for(int y = 0; y < gol.dimY; y++) {
board[x][y] = seed.nextInt(100) < gol.fillperc;
}
}
return this;
}
public void calcBoard() {
Thread t = new Thread(new Runnable() {
#Override
public void run() {
new_board = board;
for(int x = 0; x < gol.dimX; x++) {
for(int y = 0; y < gol.dimY; y++) {
int n = getAliveNeighbours(x,y);
if(board[x][y]) {
if(n > 3 || n < 2) new_board[x][y] = false;
else new_board[x][y] = true;
} else {
if(n == 3) new_board[x][y] = true;
else new_board[x][y] = false;
}
}
}
}
});
t.start();
try {t.join();} catch (InterruptedException e) {e.printStackTrace();}
}
public int getAliveNeighbours(int x, int y) {
int n = 0;
if(isIn(x+1,y+1)) {if(board[x+1][y+1]) {n+=1;}}
if(isIn(x,y+1)) {if(board[x][y+1]) {n+=1;}}
if(isIn(x-1,y+1)) {if(board[x-1][y+1]) {n+=1;}}
if(isIn(x+1,y)) {if(board[x+1][y]) {n+=1;}}
if(isIn(x-1,y)) {if(board[x-1][y]) {n+=1;}}
if(isIn(x+1,y-1)) {if(board[x+1][y-1]) {n+=1;}}
if(isIn(x,y-1)) {if(board[x][y-1]) {n+=1;}}
if(isIn(x-1,y-1)) {if(board[x-1][y-1]) {n+=1;}}
return n;
}
public boolean isIn(int x, int y) {
if(x < 0|| x > gol.dimX-1) return false;
if(y < 0|| y > gol.dimY-1) return false;
return true;
}
public void genImage() {
Thread t = new Thread(new Runnable() {
#Override
public void run() {
BufferedImage imag = new BufferedImage(gol.dimX*gol.cellScale,gol.dimX*gol.cellScale,BufferedImage.TYPE_INT_RGB);
for(int x = 0; x < gol.dimX; x++) {
for(int y = 0; y < gol.dimY; y++) {
int c = 0x000000;
if(board[x][y]) c = 0xCCCCCC;
for(int i = 0; i < gol.cellScale; i++) {
for(int j = 0; j < gol.cellScale; j++) {
imag.setRGB(x*gol.cellScale+i, y*gol.cellScale+j, c);
}
}
}
}
img = imag;
}
});
t.start();
try {t.join();} catch (InterruptedException e) {System.out.println("wesh");}
}
public void paint(Graphics g) {
super.paint(g);
genImage();
g.drawImage(img, 0, 0, img.getWidth(), img.getHeight(), null);
}
}
thing that triggers me the most is that everything works fine and the after looking at my code I can't find what can cause the simulation to be inaccurate
new_board = board doesn't do what you want it to. It just sets one the value of the new_board variable to the current value of the board variable... both are references. That means you're modifying the same board that you're examining... whereas you want to construct a new board based solely on the contents of the old board.
Remove the new_board field (which you don't need), then change this:
new_board = board;
to
boolean[][] new_board = new boolean[gol.dimX][gol.dimY];
and then at the end of the method, add:
board = new_board;
... so that the old board is replaced with the new board. It's possible that there are other problems with your logic, but that's the most obvious one.
I'd also strongly advise you to get rid of all your threading code in calcBoard and genImage. All you're doing is creating a new thread to do something, then waiting for that thread to finish... that doesn't do any good (it still blocks until the work is complete) but it makes the code more complex.
(I'd also suggest reformatting the stop method so that it's not all on one line, which makes it really hard to read. In general, I'd suggest reformatting the whole code, in fact. Just tell your IDE to reformat the whole thing.)
My assignment is to create the game "FloodIt." You can play the game here if you need to understand it, but I don't think it's really necessary: http://unixpapa.com/floodit/
I have finished the actual game part of it, but now I need to make a graphical interface for it. I have three classes:
Board.java, which makes the board with random int values and contains several other methods to make the game work:
import java.awt.Color;
import java.util.Random;
/**The board class for the Flood-It game. This class implements a NxN board filled with numColors colors.
* The class implements several methods to allow the playing of the game.
*/
class Board {
//you will probably need to create some field variables
private int size;
private int numColors;
private int[][] board;
private int numOfMoves;
/**Constructs a new sizeXsize board filled where each element on the board is a random number between 0
* and numColors. Also initializes the number of moves to zero.
* #param size -- the size of the board
* #param numColors -- the number of possible entries on the board
*/
public Board(int size,int numColors) {
//TODO finish this constructor
this.size = size;
this.numColors = numColors;
numOfMoves = 0;
board = new int[size][size];
Random rand = new Random();
int randomNum = 0;
for (int count = 0; count < size; count++) {
for (int counter = 0; counter < size; counter++) {
randomNum = rand.nextInt(this.numColors);
board[count][counter] = randomNum;
}
}
}
/**Updates the board to fill (from the top left corner) with a specified color.
* Filling stops when any other color is hit besides the one in the top left corner.
* Play the game at http://www.lemoda.net/javascript/flood-it/ or http://unixpapa.com/floodit/?sz=14&nc=4
* to get a better understanding of what this method should do.
* You will probably also want to take a look at the algorithm described
* at http://en.wikipedia.org/wiki/Flood_fill which describes what this method should do.
* I recommend the Stack-based recursive implementation. It is a recursive algorithm for
* flooding the board. It is one of the easier ones to implement.
* You are free to have this method call other methods. I would recommend creating a private method that
* this method calls and have that private method be the recursive method.
* A recursive method is one that calls itself.
* #param color -- the new color to flood the board with.
*/
public void move(int replacementColor) {
int targetColor = board[0][0];
recursiveMove(0,0,targetColor,replacementColor);
numOfMoves++;
}
private void recursiveMove(int xCoord, int yCoord, int targetColor, int replacementColor) {
if (targetColor == replacementColor) {
return;
}
if (board[xCoord][yCoord] != targetColor) {
return;
}
board[xCoord][yCoord] = replacementColor;
if (yCoord != size-1) {
recursiveMove(xCoord,yCoord+1,targetColor,replacementColor);
}
if (yCoord != 0) {
recursiveMove(xCoord,yCoord-1,targetColor,replacementColor);
}
if (xCoord != 0) {
recursiveMove(xCoord-1,yCoord,targetColor,replacementColor);
}
if (xCoord != size-1) {
recursiveMove(xCoord+1,yCoord,targetColor,replacementColor);
}
}
/**returns true if the board is not completely filled with a single color.
* Otherwise it returns false.
* #return true if board is all one color
*/
public boolean finished() {
//TODO finish this method
for (int count = 0; count < size; count++) {
for (int counter = 0; counter < size; counter++) {
if (board[count][counter] != board[0][0]) {
return false;
}
}
}
return true;
}
/**returns how many times the move() method has been called.
* #return the number of times the move() method has been called.
*/
public int numMoves() {
//TODO finish this method
return numOfMoves;
}
/**Returns a string representation of the board. Use tabs between elements of the board.
* And have every row of the board be separated by a newline character.
* Example:
* "1\t0\t3\t\n2\t0\t2\t\n1\t0\t1\t\n"
* #return a String representation of the board
*/
public String toString() {
//TODO finish this method
String boardString = "";
for (int count = 0; count < board.length; count++) {
for (int counter = 0; counter < board.length; counter++) {
boardString += board[count][counter];
boardString += "\t";
}
boardString += "\n";
}
return boardString;
}
}
FloodIt.java, which contains the JFrame lines in order to load the graphical interface, as well as code to actually run the game (it's not entirely finished, as I got stuck):
import java.util.Scanner;
import javax.swing.JFrame;
/**This class is the main method for the Flood-It game as found on many web sites
* ( such as http://www.lemoda.net/javascript/flood-it/ or
http://unixpapa.com/floodit/?sz=14&nc=4 ).
* It prompts the user for the size of the board
* and the number of colors. The user is prompted for the next color until the board is flooded.
* After the game is over it prints how many turns the user took and then asks if they want to play again.
*/
class FloodIt {
private static final int FRAMESIZE = 1000;
public static void main(String args[]) {
JFrame frame = new JFrame();
frame.setSize(FRAMESIZE,FRAMESIZE);
frame.setTitle("Brennan's Game");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
GraphicalBoard component = new GraphicalBoard();
frame.add(component);
frame.setVisible(true);
String again="";
int size = 20;
int numColors = 7;
do {
Board board=new Board(size,numColors);
while(!board.finished()) {
//I will change the print statements below into graphical input boxes later
System.out.print("****************\n"+board+"\n****************\n");
System.out.print("What color do you choose? ");
int color=Integer.parseInt(scan.nextLine());
board.move(color);
}
System.out.println("Nice job, you finished in "+board.numMoves());
System.out.print("Would you like to play again (Y/N)? ");
again=scan.nextLine();
} while (again.equalsIgnoreCase("Y"));
scan.close();
}
}
And GraphicalBoard.java, which is supposed to take the values of the 2d array from Board.java and display the board in a graphical interface. Each number that could be in the 2d array corresponds with a color in the Colors array:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import javax.swing.JComponent;
public class GraphicalBoard extends JComponent {
private int xSize = 50;
private int ySize = 50;
public void paintComponent(Graphics g, int size, Board board) {
String colors[] = {"BLUE","GREEN","YELLOW","RED","BLACK","ORANGE","PINK"};
Graphics2D g2 = (Graphics2D) g;
int xCoord = 0;
int yCoord = 0;
int colorNum = 0;
String colorOfSquare = "";
for (int count = 0; count < size; count++) {
for (int counter = 0; counter < size; counter++) {
colorNum = board[count][counter];
colorOfSquare = colors[colorNum];
g2.setColor(Color.colorOfSquare);
Rectangle square = new Rectangle(xCoord,yCoord,xSize,ySize);
xCoord += 50;
}
yCoord += 50;
}
}
}
Two problems:
In GraphicalBoard.java, on the line "colorNum = board[count][counter];", I am getting the error: "The type of expression must be an array type but it resolved to Board."
I seem to be having a problem bring over the already initialized board from the Board.java class into the GraphicalBoard.java class.
In GraphicalBoard.java, on the line "g2.setColor(Color.colorOfSquare);", I am getting the error: "colorOfSquare cannot be resolved or it not a field."
I know the problem, it is supposed to be something like "g2.setColor(Color.BLACK);", but I am going to have the user input the color, so it kind of needs to be a variable and I was hoping to have something cleaner than just an if statement for every color.
Any suggestions? Thanks!
Your Board class contains a member variable int[][] board, but its scope is private. When you call the following:
colorNum = board[count][counter];
This is wrong because the board variable here is an object of Board class. It itself is not the two day array, but it encapsulates int[][] board inside it. So you need to provide a getter method in Board to expose its board member variable like this:
public int[][] getBoard() {
return board;
}
Then in the paintComponent method you can access it as: board.getBoard()[count][counter].
You already seem to have a user inputted color in the colorOfSquare variable. But Graphics2D's setColor method would only accept a variable of type java.awt.Color. Since you have the String representation of the color, you can get its corresponding java.awt.Color value using reflection as mentioned here. The following should work for you:
Color color;
try {
Field field = Color.class.getField(colorOfSquare);
color = (Color) field.get(null);
} catch (Exception e) {
color = null; // Not defined
}
Two answers:
paintComponent ONLY receives a Graphics object. See this link for a short tutorial. If you need to access other objects in this method, make them variables of GraphicalBoard and pass them om during construction.
1.5 You need to access the Board's board, as this is what you are using. So add a getBoard(int i, int j) in class Board. Something like the following (I also added a getSize() method) :
public int getBoard(int i, int j) {
return board[i][j] ;
}
public int getSize() {
return size;
}
Your color colorOfSquare is already defined as a color. The error arises because the Color class doesn't have such a constant. You should just pass the color directly.
Try this:
public class GraphicalBoard extends JComponent {
private int xSize = 50;
private int ySize = 50;
private Board board;
private int size;
public GraphicalBoard() {
}
public void setBoard(Board board){
this.board = board;
}
public void paintComponent(Graphics g) {
super.paintComponent();
if(board == null) {
throw new RuntimeException("Board not set") ;
}
String colors[] = {"BLUE","GREEN","YELLOW","RED","BLACK","ORANGE","PINK"};
Graphics2D g2 = (Graphics2D) g;
int xCoord = 0;
int yCoord = 0;
int colorNum = 0;
int size = board.getSize() ;
String colorOfSquare = "";
for (int count = 0; count < size; count++) {
for (int counter = 0; counter < size; counter++) {
colorNum = board.getBoard(count, counter) ;
colorOfSquare = colors[colorNum];
g2.setColor(colorOfSquare);
Rectangle square = new Rectangle(xCoord,yCoord,xSize,ySize);
xCoord += 50;
}
yCoord += 50;
}
}
In very general terms,
your view, here the drawing JPanel, should contain a reference to the model object via a has-a or "composition" structure
The view should be notified of changes in the model, often via event listeners such as a PropertyChangeListener
The view then extracts the key information and uses that to help decide what to draw. So, if it has a reference to the current Board object, it can make getter method calls from within the paintComponent method.
Other issues in your code:
Make sure to call the super's paintComponent within your override, else you will not clean up "dirty" pixels
Don't mix linear code -- a Scanner based on System.in with GUI code. Make it all one or the other.
For example, in the code below, I use a model class, the class that holds the int[][] board variable and here called BoardModel, and I give it a SwingPropertyChangeSupport object.
class BoardModel {
// .....
private SwingPropertyChangeSupport support = new SwingPropertyChangeSupport(this);
This object will accept listeners, and will allow me to notify listeners of changes to the model.
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
support.addPropertyChangeListener(propertyName, listener);
}
Then when the model changes, I notify all listeners by calling the support object's firePropertyChange(...) method:
public void selectSquare(int x, int y) {
int replacementValue = board[y][x];
int targetValue = board[0][0];
if (targetValue == replacementValue) {
return;
} else {
recursiveMove(0, 0, targetValue, replacementValue);
numOfMoves++;
support.firePropertyChange(BOARD, null, board); // ***** here
setWin(checkForWin());
}
}
Then in the control, I can add listeners that notify the view of changes:
model.addPropertyChangeListener(BoardModel.BOARD, new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent e) {
view.repaint();
String moveCount = "" + model.getNumOfMoves();
controlPanel.setMoveCountFieldText(moveCount);
}
});
model.addPropertyChangeListener(BoardModel.WIN, new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ((boolean) evt.getNewValue()) {
String message = "Move count: " + model.getNumOfMoves();
String title = "Game Over";
int messageType = JOptionPane.PLAIN_MESSAGE;
JOptionPane.showMessageDialog(view, message, title, messageType);
}
}
});
A working example could look like this:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Random;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.SwingPropertyChangeSupport;
public class BoardFun {
private static final int NUM_COLORS = 6;
private static final int SIZE = 20;
#SuppressWarnings("serial")
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
int size = SIZE;
int numColors = NUM_COLORS;
final BoardModel model = new BoardModel(size , numColors );
final BoardPanel view = new BoardPanel();
final ControlPanel controlPanel = new ControlPanel();
view.setModel(model);
view.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent mEvt) {
Point p = mEvt.getPoint();
int row = view.getRow(p);
int col = view.getColumn(p);
model.selectSquare(col, row);
}
});
model.addPropertyChangeListener(BoardModel.BOARD, new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent e) {
view.repaint();
String moveCount = "" + model.getNumOfMoves();
controlPanel.setMoveCountFieldText(moveCount);
}
});
model.addPropertyChangeListener(BoardModel.WIN, new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ((boolean) evt.getNewValue()) {
String message = "Move count: " + model.getNumOfMoves();
String title = "Game Over";
int messageType = JOptionPane.PLAIN_MESSAGE;
JOptionPane.showMessageDialog(view, message, title, messageType);
}
}
});
controlPanel.setResetAction(new AbstractAction("Reset") {
#Override
public void actionPerformed(ActionEvent e) {
model.reset();
}
});
JFrame frame = new JFrame("Game");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(view);
frame.add(controlPanel, BorderLayout.PAGE_START);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
#SuppressWarnings("serial")
class ControlPanel extends JPanel {
private JTextField moveCountField = new JTextField("0", 10);
private JButton resetButton = new JButton();
public ControlPanel() {
add(new JLabel("Move Count:"));
add(moveCountField);
add(resetButton);
}
public void setResetAction(Action action) {
resetButton.setAction(action);
}
public void setMoveCountFieldText(String text) {
moveCountField.setText(text);
}
}
#SuppressWarnings("serial")
class BoardPanel extends JPanel {
private static final int PREF_W = 640;
private static final int PREF_H = PREF_W;
private BoardModel model;
private Color[] colors;
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
} else {
return new Dimension(PREF_W, PREF_H);
}
}
public void setModel(BoardModel model) {
this.model = model;
colors = new Color[model.getNumColors()];
// create colors.length Colors, all of different hue
for (int i = 0; i < colors.length; i++) {
float hue = (float) i / colors.length;
colors[i] = Color.getHSBColor(hue, 1f, 1f);
}
}
// translate point to logical square position
int getRow(Point p) {
return (p.y * model.getBoard().length) / getHeight();
}
int getColumn(Point p) {
return (p.x * model.getBoard()[0].length) / getWidth();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); // always call the super's method
if (model == null) {
return;
}
int board[][] = model.getBoard();
int height = getHeight() / board.length;
int width = getWidth() / board[0].length;
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[i].length; j++) {
Color color = colors[board[i][j]];
g.setColor(color);
int x = (j * getWidth()) / board[0].length;
int y = (i * getHeight()) / board.length;
g.fillRect(x, y, width, height);
}
}
}
}
class BoardModel {
public static final String BOARD = "board";
public static final String WIN = "win";
private int[][] board;
private int numColors;
private Random random = new Random();
private SwingPropertyChangeSupport support = new SwingPropertyChangeSupport(this);
private int numOfMoves = 0;
private boolean win = false;
public BoardModel(int size, int numColors) {
board = new int[size][size];
this.numColors = numColors;
reset();
}
public void reset() {
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[i].length; j++) {
board[i][j] = random.nextInt(numColors);
}
}
numOfMoves = 0;
support.firePropertyChange(BOARD, null, board);
setWin(false);
}
public int[][] getBoard() {
return board;
}
public int getNumOfMoves() {
return numOfMoves;
}
public int getNumColors() {
return numColors;
}
public void setWin(boolean win) {
boolean oldValue = this.win;
boolean newValue = win;
this.win = win;
support.firePropertyChange(WIN, oldValue, newValue);
}
public boolean isWin() {
return win;
}
public void selectSquare(int x, int y) {
int replacementValue = board[y][x];
int targetValue = board[0][0];
if (targetValue == replacementValue) {
return;
} else {
recursiveMove(0, 0, targetValue, replacementValue);
numOfMoves++;
support.firePropertyChange(BOARD, null, board);
setWin(checkForWin());
}
}
public boolean checkForWin() {
int value = board[0][0];
for (int[] row : board) {
for (int cell : row) {
if (cell != value) {
return false;
}
}
}
return true;
}
private void recursiveMove(int i, int j, int targetValue, int replacementValue) {
int currentValue = board[i][j];
if (currentValue != targetValue || currentValue == replacementValue) {
return;
}
board[i][j] = replacementValue;
int rowMin = Math.max(0, i - 1);
int rowMax = Math.min(board.length - 1, i + 1);
int colMin = Math.max(0, j - 1);
int colMax = Math.min(board[i].length - 1, j + 1);
for (int i2 = rowMin; i2 <= rowMax; i2++) {
if (i2 != i) {
recursiveMove(i2, j, targetValue, replacementValue);
}
}
for (int j2 = colMin; j2 <= colMax; j2++) {
if (j2 != j) {
recursiveMove(i, j2, targetValue, replacementValue);
}
}
}
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
support.addPropertyChangeListener(propertyName, listener);
}
}
So I am doing a project where I am to create a 2d simulation of different "critters". These critters interact with each other on the GUI and will fight each other. My issue is that I am using a class called Simulation which is used to simulate the 2d environment. In this class I am using lines such as
if (c.getSpecies() == Species.ANT)
//do something
This given me an error telling me that the symbol Species or ANT cannot be found. I am pretty sure this is due to Simulation not having any inheritance from my main *GVcritter** class and Simulation should not have any inheritance. That being said, I am not quite sure how to approach doing this without using if statements like the one above.
Main GVcritter class:
import java.awt.*;
/***********************************************************
GVcritter represents a generic critter with several
characteristics: location, species, color and the number
of steps taken. All other critters in the simulation
extend this class and add a few methods.
#author Scott Grissom
#version August 2016
***********************************************************/
public abstract class GVcritter {
/** critter location */
protected Location myLocation;
/** critter color */
private Color myColor;
/** critter species */
private Species mySpecies;
/** number of steps taken during the simulation */
protected int steps;
/***********************************************************
These enubmerated types are used throughout the simulation
classes.
***********************************************************/
public static enum Direction {
NORTH, SOUTH, EAST, WEST, NONE
};
public static enum Attack {
ROAR, POUNCE, SCRATCH, FORFEIT
};
public static enum Species {
NONE, ANT, BIRD, HIPPO, VULTURE, TIGER
};
/***********************************************************
These abstract methods MUST BE IMPLEMENTED by all classes
that extend GVcritter.
***********************************************************/
public abstract Attack getAttack(GVcritter opponent);
public abstract Direction getMoveDirection();
/***********************************************************
Instantiate and initialize the instance variables.
#param l location of the critter
***********************************************************/
public GVcritter(Location loc){
myLocation = loc;
myColor = Color.WHITE;
mySpecies = Species.NONE;
steps = 0;
}
/***********************************************************
Returns the critter species
#returns the species
***********************************************************/
public final Species getSpecies(){
return mySpecies;
}
/***********************************************************
Sets the critter species
#param s the species
***********************************************************/
public final void setSpecies(Species s){
mySpecies = s;
}
/***********************************************************
Returns the critter color
#returns the color
***********************************************************/
public final Color getColor(){
return myColor;
}
/***********************************************************
Sets the critter color
#param c the color
***********************************************************/
public final void setColor(Color c){
myColor = c;
}
/***********************************************************
Sets the critter location
#param loc the location
***********************************************************/
public final void setLocation(Location loc){
myLocation = loc;
}
/***********************************************************
Returns the critter location
#returns the location
***********************************************************/
public final Location getLocation(){
return myLocation;
}
}
Simulation class:
import java.util.*;
import javax.swing.*;
import java.awt.*;
/****************************************************
* Simulates a 2D world of critters that move around
* and fight if they inhabit the same location.
*
* #author Scott Grissom
* #version August 2016
***************************************************/
public class Simulation extends GVcritter{
Random gen = new Random();
/** a 2D world of critters */
private GVcritter[][] theWorld;
/** a collection of all live critters */
private ArrayList <GVcritter> allCritters;
/** control size of the world */
private final int ROWS=50, COLUMNS=70, SIZE=10;
/** number of Ants alive in the simulation */
private int numAnts;
private int stepCount, numBirds, numHippos, numVultures, numTigers;
/****************************************************
Constructor instantiates and initializes all
instance members.
****************************************************/
public Simulation(){
theWorld = new GVcritter[ROWS][COLUMNS];
allCritters = new ArrayList<GVcritter>();
numAnts=0;
stepCount = 0;
numAnts = 0;
numBirds = 0;
numHippos = 0;
numVultures = 0;
// set the appropriate size of the invisibile drawing area
setPreferredSize(new Dimension(COLUMNS*SIZE, ROWS*SIZE));
}
/****************************************************
Add the requested number of Ants into the simulation.
Repeatedly ask for a random location that is free.
Increment the number of Ants in the simulation.
#param num number of ants
****************************************************/
public void addAnts(int num){
numAnts += num;
for(int i=1;i<=num;i++){
// create a new Ant at an open location
Location loc = getOpenLocation();
Ant c = new Ant(loc);
placeCritter(c);
}
}
public void addBirds(int num) {
numBirds += num;
for (int i=1; i<=num; i++)
{
Location loc = getOpenLocation();
Bird c = new Bird(loc);
placeCritter(c);
}
}
public void addHippos(int num) {
numHippos += num;
for (int i=1; i<=num; i++)
{
Location loc = getOpenLocation();
Hippo c = new Hippo(loc);
placeCritter(c);
}
}
public void addVultures(int num) {
numVultures += num;
for (int i=1; i<=num; i++)
{
Location loc = getOpenLocation();
Vulture c = new Vulture(loc);
placeCritter(c);
}
}
public void addTigers(int num) {
numTigers += num;
for (int i=1; i<=num; i++)
{
Location loc = getOpenLocation();
Tiger c = new Tiger(loc);
placeCritter(c);
}
}
/******************************************************
Move forward on step of the simulation
*****************************************************/
/* public void oneStep(){
// shuffle the arraylist of critters for better performance
Collections.shuffle(allCritters);
stepCount++;
// step throgh all critters using traditional for loop
for(int i=0; i<allCritters.size(); i++){
GVcritter attacker = allCritters.get(i);
// what location does critter want to move to?
GVcritter.Direction dir = attacker.getMoveDirection();
Location previousLoc = attacker.getLocation();
Location nextLoc = getRelativeLocation(previousLoc, dir);
// who is at the next location?
GVcritter defender = theWorld[nextLoc.getRow()][nextLoc.getColumn()];
// no critters here so OK for critter 1 to move
if(defender == null){
theWorld[nextLoc.getRow()][nextLoc.getColumn()] = attacker;
attacker.setLocation(nextLoc);
theWorld[previousLoc.getRow()][previousLoc.getColumn()] = null;
// both critters the same species so peacefully bypass
}else if(attacker.getSpecies() == defender.getSpecies()){
// update critter locations
attacker.setLocation(nextLoc);
defender.setLocation(previousLoc);
// update positions in the world
theWorld[nextLoc.getRow()][nextLoc.getColumn()] = attacker;
theWorld[previousLoc.getRow()][previousLoc.getColumn()] = defender;
//different species so they fight at location of critter 2
}else if(attacker.getSpecies() != defender.getSpecies()){
fight(attacker, defender);
}
}
// update drawing of the world
repaint();
}*/
/******************************************************
Step through the 2D world and paint each location white
(for no critter) or the critter's color. The SIZE of
each location is constant.
#param g graphics element used for display
*****************************************************/
public void paintComponent(Graphics g){
for(int row=0; row<ROWS; row++){
for(int col=0; col<COLUMNS; col++){
GVcritter c = theWorld[row][col];
// set color to white if no critter here
if(c == null){
g.setColor(Color.WHITE);
// set color to critter color
}else{
g.setColor(c.getColor());
}
// paint the location
g.fillRect(col*SIZE, row*SIZE, SIZE, SIZE);
}
}
}
public String getStats() {
return "Steps: " + stepCount + "\nAnts: " + numAnts + "\nBirds: " + numBirds + "\nHippos: " + numHippos + "\nVultures: " + numVultures;
}
private Location getOpenLocation() {
int randRow, randCol;
boolean isEmpty = false;
Location loc = new Location();
do {
randRow = gen.nextInt(50);
randCol = gen.nextInt(70);
if (theWorld[randRow][randCol] == null)
isEmpty = true;
} while (!isEmpty);
loc.setRow(randRow);
loc.setColumn(randCol);
return loc;
}
private void placeCritter(GVcritter c) {
Location critterLoc = c.getLocation();
int row = critterLoc.getRow(), col = critterLoc.getColumn();
allCritters.add(c);
theWorld[row][col] = c;
}
private Location getRelativeLocation(Location loc, GVcritter.Direction d) {
int row = loc.getRow(), col = loc.getColumn();
Location neighbor = new Location();
switch(d) {
case NORTH:
if (row == 0)
{
neighbor.setRow(ROWS);
neighbor.setColumn(col);
}
else
{
neighbor.setRow(row - 1);
neighbor.setColumn(col);
}
break;
case EAST:
if (col == COLUMNS)
{
neighbor.setRow(row);
neighbor.setColumn(0);
}
else
{
neighbor.setRow(row);
neighbor.setColumn(col + 1);
}
break;
case SOUTH:
if (row == ROWS)
{
neighbor.setRow(0);
neighbor.setColumn(col);
}
else
{
neighbor.setRow(row - 1);
neighbor.setColumn(col);
}
break;
case WEST:
if (col == 0)
{
neighbor.setRow(row);
neighbor.setColumn(COLUMNS);
}
else
{
neighbor.setRow(row);
neighbor.setColumn(col - 1);
}
break;
}
return neighbor;
}
public void reset() {
for (int i=0; i<ROWS; i++)
for (int j=0; i<COLUMNS; j++)
theWorld[i][j] = null;
allCritters.clear();
numAnts = 0;
stepCount = 0;
numBirds = 0;
numHippos = 0;
numVultures = 0;
}
private void critterDies(GVcritter c) {
int location;
if (c.getSpecies() == Species.ANT) {
numAnts--;
location = 0;
for (GVcritter e: allCritters)
{
if (e.get(location) == c)
e.remove(location);
location++;
}
}
else if (c == Species.BIRD) {
numBirds--;
location = 0;
for (GVcritter e: allCritters)
{
if (e.get(location) == c)
e.remove(location);
location++;
}
}
else if (c == Species.HIPPO) {
numHippos--;
location = 0;
for (GVcritter e: allCritters)
{
if (e.get(location) == c)
e.remove(location);
location++;
}
}
else if (c == Species.VULTURE) {
numVultures--;
location = 0;
for (GVcritter e: allCritters)
{
if (e.get(location) == c)
e.remove(location);
location++;
}
}
else if (c == Species.TIGER) {
numTigers--;
location = 0;
for (GVcritter e: allCritters)
{
if (e.get(location) == c)
e.remove(location);
location++;
}
}
}
public void fight(GVcritter attacker, GVcritter defender) {
Location attackLoc = attacker.getLocation();
Location defendLoc = defender.getLocation();
int attackRow = attackLoc.getRow(), attackCol = attackLoc.getColumn();
int defendRow = defendLoc.getRow(), defendCol = defendLoc.getColumn();
theWorld[attackRow][attackCol] = null;
if (attackerWins(attacker, defender))
{
//critterDies(defender);
theWorld[defendRow][defendCol] = attacker;
}
else
{
//critterDies(attacker);
}
}
private boolean attackerWins(GVcritter attacker, GVcritter defender) {
if (attacker.getAttack(defender) == Attack.POUNCE && defender.getAttack(attacker) == Attack.ROAR)
return true;
else if (attacker.getAttack(defender) == Attack.POUNCE && defender.getAttack(attacker) == Attack.SCRATCH)
return false;
else if (attacker.getAttack(defender) == Attack.SCRATCH && defender.getAttack(attacker) == Attack.ROAR)
return false;
else if (attacker.getAttack(defender) == Attack.SCRATCH && defender.getAttack(attacker) == Attack.POUNCE)
return true;
else if (attacker.getAttack(defender) == Attack.ROAR && defender.getAttack(attacker) == Attack.SCRATCH)
return true;
else if (attacker.getAttack(defender) == Attack.ROAR && defender.getAttack(attacker) == Attack.POUNCE)
return false;
else
{
if (Math.random() < 0.5)
return true;
else
return false;
}
}
}
My problems are occuring in my critterDies(), fight(), and attackerWins() methods. Any help is appreciated!
Species.ANT is not a top-level class. Since
it is an inner class of GVcritter, change
Species.ANT to GVcritter.Species.ANT.
I'm coding a game for my final project in Java, our teacher provided us with a Board class that is a component that allows us to place and remove pegs on a virtual game board instead of having to code one ourselves. I'm trying to add Key Binding to the Board component but the action I want performed on key press is happening when I run the program but It won't run when I type a Key.
The board class already has a method for getting the position clicked on the component and I think this might be interfering with my Code but I'm not sure.
This is my game class where I tried to add keybinding
package rpgGame;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
public class RPGGame
{
public static final GameWorld WORLD_MAP = new GameWorld();
public static Board LOCAL_MAP = new Board(20,50);
public static List<Mobile> allMobs = new ArrayList<Mobile>();
public static final Player PLAYER = new Player();
public static int xIndex = ((GameWorld.WORLD_SIZE-1)/2) - (50/2);
public static int yIndex = ((GameWorld.WORLD_SIZE-1)/2) - (20/2);
public static boolean boardUpdate = true;
public enum Direction {RIGHT,LEFT,UP,DOWN}
private static final String MOVE_PLAYER_UP = "move up";
private static final String MOVE_PLAYER_LEFT = "move left";
private static final String MOVE_PLAYER_RIGHT = "move right";
private static final String MOVE_PLAYER_DOWN = "move down";
public static final Thread SYNC_BOARD = new Thread()
{
public synchronized void run()
{
while (boardUpdate)
{
for (int i = 0; i < 50; i++)
{
for (int j = 0; j < 20; j++)
{
if (WORLD_MAP.isOccupied(i+xIndex, j+yIndex))
{
LOCAL_MAP.putPeg(Color.RED, j, i);
System.out.println("Successfully Updated");
}
else
{
LOCAL_MAP.putPeg(Color.GRAY, j,i);
}
}
}
boardUpdate = false;
}
}
};
public RPGGame()
{
generateMobs(200);
placeMobs();
placePlayer();
SYNC_BOARD.run();
LOCAL_MAP.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), MOVE_PLAYER_UP);
LOCAL_MAP.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0), MOVE_PLAYER_UP);
LOCAL_MAP.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), MOVE_PLAYER_LEFT);
LOCAL_MAP.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0), MOVE_PLAYER_LEFT);
LOCAL_MAP.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), MOVE_PLAYER_RIGHT);
LOCAL_MAP.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0), MOVE_PLAYER_RIGHT);
LOCAL_MAP.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), MOVE_PLAYER_DOWN);
LOCAL_MAP.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0), MOVE_PLAYER_DOWN);
LOCAL_MAP.getActionMap().put(MOVE_PLAYER_UP, new MoveAction(Direction.UP));
LOCAL_MAP.getActionMap().put(MOVE_PLAYER_LEFT, new MoveAction(Direction.LEFT));
LOCAL_MAP.getActionMap().put(MOVE_PLAYER_RIGHT, new MoveAction(Direction.RIGHT));
LOCAL_MAP.getActionMap().put(MOVE_PLAYER_DOWN, new MoveAction(Direction.DOWN));
}
public static void main(String[] args)
{
new RPGGame();
}
public static void generateMobs(int numOfMobs)
{
for (int i=0; i<numOfMobs; i++)
{
allMobs.add(new Mobile());
}
}
public static void generateMobs()
{
int numOfMobs = (int)(Math.random()*500);
for (int i=0;i<numOfMobs; i++)
{
allMobs.add(new Mobile());
}
}
public static void placeMobs()
{
for (int i=0; i<allMobs.size(); i++)
{
//i is used as a placeholder value for points until I create a random number generator.
WORLD_MAP.placeCharacter(i, i,allMobs.get(i));
allMobs.get(i).setLocation(i, i);
}
}
public static void placePlayer()
{
WORLD_MAP.placeCharacter(249, 249, PLAYER);
PLAYER.setLocation(249, 249);
}
#SuppressWarnings("serial")
public class MoveAction extends AbstractAction
{
Direction direction;
public MoveAction(Direction direction)
{
if (direction.equals(Direction.RIGHT))
{
int x = PLAYER.getX();
int y = PLAYER.getY();
WORLD_MAP.moveCharacter(x+1, y, x, y);
PLAYER.move(1, 0);
boardUpdate = true;
System.out.println("MOVE RIGHT");
}
if (direction.equals(Direction.LEFT))
{
int x = PLAYER.getX();
int y = PLAYER.getY();
WORLD_MAP.moveCharacter(x, y, x-1, y);
PLAYER.move(-1, 0);
boardUpdate = true;
System.out.println("MOVE LEFT");
}
if (direction.equals(Direction.UP))
{
int x = PLAYER.getX();
int y = PLAYER.getY();
WORLD_MAP.moveCharacter(x, y, x, y+1);
PLAYER.move(0, 1);
boardUpdate = true;
System.out.println("MOVE UP");
}
if (direction.equals(Direction.DOWN))
{
int x = PLAYER.getX();
int y = PLAYER.getY();
WORLD_MAP.moveCharacter(x, y, x, y-1);
PLAYER.move(0, -1);
boardUpdate = true;
System.out.println("MOVE DOWN");
}
}
#Override
public void actionPerformed(ActionEvent e)
{
}
}
}
This is the Board class
package rpgGame;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
/** Board GUI for implementation with various games
* Author: Kirill Levin, Troy Vasiga, Chris Ingram
*/
#SuppressWarnings("serial")
public class Board extends JPanel
{
private static final int X_DIM = 60;
private static final int Y_DIM = 60;
private static final int X_OFFSET = 30;
private static final int Y_OFFSET = 30;
private static final double MIN_SCALE = 0.25;
private static final int GAP = 10;
private static final int FONT_SIZE = 16;
// Grid colours
private static final Color GRID_COLOR_A = new Color(153,255,102);
private static final Color GRID_COLOR_B = new Color(136,255,77);
private Color[][] grid;
private Point lastClick; // How the mouse handling thread communicates
// to the board where the last click occurred
private String message = "";
private int numLines = 0;
private double[][] line = new double[4][100]; // maximum number of lines is 100
private int columns, rows;
private int originalWidth;
private int originalHeight;
private double scale;
/** A constructor to build a 2D board.
*/
public Board (int rows, int columns)
{
super( true );
JFrame boardFrame = new JFrame( "Board game" );
this.columns = columns;
this.rows = rows;
originalWidth = 2*X_OFFSET+X_DIM*columns;
originalHeight = 2*Y_OFFSET+Y_DIM*rows+GAP+FONT_SIZE;
this.setPreferredSize( new Dimension( originalWidth, originalHeight ) );
boardFrame.setResizable(true);
this.grid = new Color[columns][rows];
this.addMouseListener(
new MouseInputAdapter()
{
/** A method that is called when the mouse is clicked
*/
public void mouseClicked(MouseEvent e)
{
int x = (int)e.getPoint().getX();
int y = (int)e.getPoint().getY();
// We need to by synchronized to the parent class so we can wake
// up any threads that might be waiting for us
synchronized(Board.this)
{
int curX = (int)Math.round(X_OFFSET*scale);
int curY = (int)Math.round(Y_OFFSET*scale);
int nextX = (int)Math.round((X_OFFSET+X_DIM*grid.length)*scale);
int nextY = (int)Math.round((Y_OFFSET+Y_DIM*grid[0].length)*scale);
// Subtract one from high end so clicks on the black edge
// don't yield a row or column outside of board because of
// the way the coordinate is calculated.
if (x >= curX && y >= curY && x < nextX && y < nextY)
{
lastClick = new Point(y,x);
// Notify any threads that would be waiting for a mouse click
Board.this.notifyAll() ;
} /* if */
} /* synchronized */
} /* mouseClicked */
} /* anonymous MouseInputAdapater */
);
boardFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
boardFrame.setContentPane( this );
boardFrame.pack();
boardFrame.setVisible(true);
}
/** A constructor to build a 1D board.
*/
public Board (int cols)
{
this(1, cols);
}
private void paintText(Graphics g)
{
g.setColor( this.getBackground() );
g.setFont(new Font(g.getFont().getFontName(), Font.ITALIC+Font.BOLD, (int)(Math.round(FONT_SIZE*scale))));
int x = (int)Math.round(X_OFFSET*scale);
int y = (int)Math.round((Y_OFFSET+Y_DIM*grid[0].length)*scale + GAP ) ;
g.fillRect(x,y, this.getSize().width, (int)Math.round(GAP+FONT_SIZE*scale) );
g.setColor( Color.black );
g.drawString(message, x, y + (int)Math.round(FONT_SIZE*scale));
}
private void paintGrid(Graphics g)
{
for (int i = 0; i < this.grid.length; i++)
{
for (int j = 0; j < this.grid[i].length; j++)
{
if ((i%2 == 0 && j%2 != 0) || (i%2 != 0 && j%2 == 0))
g.setColor(GRID_COLOR_A);
else
g.setColor(GRID_COLOR_B);
int curX = (int)Math.round((X_OFFSET+X_DIM*i)*scale);
int curY = (int)Math.round((Y_OFFSET+Y_DIM*j)*scale);
int nextX = (int)Math.round((X_OFFSET+X_DIM*(i+1))*scale);
int nextY = (int)Math.round((Y_OFFSET+Y_DIM*(j+1))*scale);
int deltaX = nextX-curX;
int deltaY = nextY-curY;
g.fillRect( curX, curY, deltaX, deltaY );
Color curColour = this.grid[i][j];
if (curColour != null) // Draw pegs if they exist
{
g.setColor(curColour);
g.fillOval(curX+deltaX/4, curY+deltaY/4, deltaX/2, deltaY/2);
}
}
}
((Graphics2D) g).setStroke( new BasicStroke(0.5f) );
g.setColor(Color.BLACK);
int curX = (int)Math.round(X_OFFSET*scale);
int curY = (int)Math.round(Y_OFFSET*scale);
int nextX = (int)Math.round((X_OFFSET+X_DIM*grid.length)*scale);
int nextY = (int)Math.round((Y_OFFSET+Y_DIM*grid[0].length)*scale);
g.drawRect(curX, curY, nextX-curX, nextY-curY);
}
private void drawLine(Graphics g)
{
for (int i =0; i < numLines; i++ )
{
((Graphics2D) g).setStroke( new BasicStroke( 5.0f*(float)scale) );
g.drawLine( (int)Math.round((X_OFFSET+X_DIM/2.0+line[0][i]*X_DIM)*scale),
(int)Math.round((Y_OFFSET+Y_DIM/2.0+line[1][i]*Y_DIM)*scale),
(int)Math.round((X_OFFSET+X_DIM/2.0+line[2][i]*X_DIM)*scale),
(int)Math.round((Y_OFFSET+Y_DIM/2.0+line[3][i]*Y_DIM)*scale) );
}
}
/**
* Convert a String to the corresponding Color defaulting to Black
* with an invald input
*/
/*private Color convertColour( String theColour )
{
for( int i=0; i<COLOUR_NAMES.length; i++ )
{
if( COLOUR_NAMES[i].equalsIgnoreCase( theColour ) )
return COLOURS[i];
}
return DEFAULT_COLOUR;
}*/
/** The method that draws everything
*/
public void paintComponent( Graphics g )
{
this.setScale();
this.paintGrid(g);
this.drawLine(g);
this.paintText(g);
}
public void setScale()
{
double width = (0.0+this.getSize().width) / this.originalWidth;
double height = (0.0+this.getSize().height) / this.originalHeight;
this.scale = Math.max( Math.min(width,height), MIN_SCALE );
}
/** Sets the message to be displayed under the board
*/
public void displayMessage(String theMessage)
{
message = theMessage;
this.repaint();
}
/** This method will save the value of the colour of the peg in a specific
* spot. theColour is restricted to
* "yellow", "blue", "cyan", "green", "pink", "white", "red", "orange"
* Otherwise the colour black will be used.
*/
public void putPeg(Color colour, int row, int col)
{
this.grid[col][row] = colour;
this.repaint();
}
/** Same as putPeg above but for 1D boards
*/
public void putPeg(Color colour, int col)
{
this.putPeg(colour, 0, col );
}
/** Remove a peg from the gameboard.
*/
public void removePeg(int row, int col)
{
this.grid[col][row] = null;
repaint();
}
/** Same as removePeg above but for 1D boards
*/
public void removePeg(int col)
{
this.grid[col][0] = null;
repaint();
}
/** Draws a line on the board using the given co-ordinates as endpoints
*/
public void drawLine(double row1, double col1, double row2, double col2)
{
this.line[0][numLines]=col1;
this.line[1][numLines]=row1;
this.line[2][numLines]=col2;
this.line[3][numLines]=row2;
this.numLines++;
repaint();
}
/** Removes one line from a board given the co-ordinates as endpoints
* If there is no such line, nothing happens
* If multiple lines, all copies are removed
*/
public void removeLine(int row1, int col1, int row2, int col2)
{
int curLine = 0;
while (curLine < this.numLines)
{
// Check for either endpoint being specified first in our line table
if ( (line[0][curLine] == col1 && line[1][curLine] == row1 &&
line[2][curLine] == col2 && line[3][curLine] == row2) ||
(line[2][curLine] == col1 && line[3][curLine] == row1 &&
line[0][curLine] == col2 && line[1][curLine] == row2) )
{
// found a matching line: overwrite with the last one
numLines--;
line[0][curLine] = line[0][numLines];
line[1][curLine] = line[1][numLines];
line[2][curLine] = line[2][numLines];
line[3][curLine] = line[3][numLines];
curLine--; // perhaps the one we copied is also a match
}
curLine++;
}
repaint();
}
/** Waits for user to click somewhere and then returns the click.
*/
public Point getClick()
{
Point returnedClick = null;
synchronized(this) {
lastClick = null;
while (lastClick == null)
{
try {
this.wait();
} catch(Exception e) {
// We'll never call Thread.interrupt(), so just consider
// this an error.
e.printStackTrace();
System.exit(-1) ;
} /* try */
}
int x = (int)Math.floor((lastClick.getY()-X_OFFSET*scale)/X_DIM/scale);
int y = (int)Math.floor((lastClick.getX()-Y_OFFSET*scale)/Y_DIM/scale);
// Put this into a new object to avoid a possible race.
returnedClick = new Point(x,y);
}
return returnedClick;
}
/** Same as getClick above but for 1D boards
*/
public double getPosition()
{
return this.getClick().getY();
}
public int getColumns()
{
return this.columns;
}
public int getRows()
{
return this.rows;
}
}
You're shooting yourself in the foot with that thread code -- you're calling run() not start() on it
SYNC_BOARD.run();
This will run on the Swing event thread and risks completely freezing your GUI.
As a general rule, you should almost never extend Thread but rather implement Runnable, but regardless, don't use that Thread code -- Instead use a Swing Timer since your code has no breaks in it, no Thread.sleeps and it will make your CPU awfully busy, and the Swing Timer will help make sure that your code obeys Swing threading rules.
Also your MoveAction is wrong. Most of that code should go in the actionPerformed method. The constructor should just set the direction field and that's it.
Something like:
#SuppressWarnings("serial")
public class MoveAction extends AbstractAction {
Direction direction;
public MoveAction(Direction direction) {
// this is the only code the constructor should have!
this.direction = direction;
}
#Override
public void actionPerformed(ActionEvent e) {
// use direction to help make move in here
}
}
Understand that this is likely causing some major problems, since the constructor is called on program creation (hence your key bindings "work" when the program starts), but it's the actionPerformed that actually gets called when the right key is pressed.
So I'm trying to run my pacman project as a jar(also tried runnable) and I just get the error message you see in the title. It runs perfectly fine in eclipse/netbeans, but whilst cleaning/building i see the warnings:
Note: C:\Users\Lucas\Documents\Eclipse\PackMan\src\game\packman\GameData.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
The main class is correct and assigned. Does anybody know what I'm doing wrong?
Here is my GameData class
public class GameData {
int mazeNo;
CopyOnWriteArrayList<Position> pills;
CopyOnWriteArrayList<Position> powerPills;
public MoverInfo packman;
public GhostInfo[] ghostInfos = new GhostInfo[4];
public int score;
Maze[] mazes;
boolean dead = false;
boolean win = false;
public GameData() {
mazes = new Maze[4];
// load mazes information
for (int m=0; m<4; m++) {
mazes[m] = new Maze(m);
}
setMaze(mazeNo);
}
private void setMaze(int m) {
packman = new MoverInfo(mazes[m].packmanPos);
for (int g=0; g<4; g++) {
ghostInfos[g] = new GhostInfo(mazes[m].ghostPos);
}
pills = new CopyOnWriteArrayList((List<Position>)(mazes[m].pills.clone()));
powerPills = new CopyOnWriteArrayList((List<Position>)(mazes[m].powerPills.clone()));
}
public void movePackMan(int reqDir) {
if (move(reqDir, packman)) {
packman.curDir = reqDir;
} else {
move(packman.curDir, packman);
}
}
private int wrap(int value, int incre, int max) {
return (value+max+incre)%max;
}
private boolean move(int reqDir, MoverInfo info) {
// current position of packman is (row, column)
int row = info.pos.row;
int column = info.pos.column;
int rows = mazes[mazeNo].rows;
int columns = mazes[mazeNo].columns;
int nrow = wrap(row, MoverInfo.DROW[reqDir], rows);
int ncol = wrap(column, MoverInfo.DCOL[reqDir], columns);
if (mazes[mazeNo].charAt(nrow, ncol) != '0') {
info.pos.row = nrow;
info.pos.column = ncol;
return true;
}
return false;
}
public void update() {
if (pills.contains(packman.pos)) {
pills.remove(packman.pos);
score += 5;
} else if (powerPills.contains(packman.pos)) {
powerPills.remove(packman.pos);
score += 50;
for (GhostInfo g:ghostInfos) {
g.edibleCountDown = 500;
}
}
for (GhostInfo g:ghostInfos) {
if (g.edibleCountDown > 0) {
if (touching(g.pos, packman.pos)) {
// eat the ghost and reset
score += 100;
g.curDir = g.reqDir = MoverInfo.LEFT;
g.pos.row = mazes[mazeNo].ghostPos.row;
g.pos.column = mazes[mazeNo].ghostPos.column;
g.edibleCountDown = 0;
}
g.edibleCountDown--;
} else {
if (touching(g.pos, packman.pos)) {
dead = true;
}
}
}
// level is cleared
if (pills.isEmpty() && powerPills.isEmpty()) {
mazeNo++;
if (mazeNo < 4) {
setMaze(mazeNo);
} else if (mazeNo == 5) {
win = true;
} else {
// game over
dead = true;
}
}
}
private boolean touching(Position a, Position b) {
return Math.abs(a.row-b.row) + Math.abs(a.column-b.column) < 3;
}
public void moveGhosts(int[] reqDirs) {
for (int i=0; i<4; i++) {
GhostInfo info = ghostInfos[i];
info.reqDir = reqDirs[i];
if (move(info.reqDir, info)) {
info.curDir = info.reqDir;
} else {
move(info.curDir, info);
}
}
}
public int getWidth() {
return mazes[mazeNo].width;
}
public int getHeight() {
return mazes[mazeNo].height;
}
public List<Integer> getPossibleDirs(Position pos) {
List<Integer> list = new ArrayList<>();
for (int d=0; d<4;d++) {
Position npos = getNextPositionInDir(pos, d);
if (mazes[mazeNo].charAt(npos.row, npos.column) != '0') {
list.add(d);
}
}
return list;
}
private Position getNextPositionInDir(Position pos, int d) {
int nrow = wrap(pos.row, MoverInfo.DROW[d], mazes[mazeNo].rows);
int ncol = wrap(pos.column, MoverInfo.DCOL[d], mazes[mazeNo].columns);
return new Position(nrow, ncol);
}
}