java swing reassign value in JButton[][] - java

I have this array of classes extending JButtons, and when one is clicked it registers that.
Then if another one gets clicked, they should 'switch' places. So my question is: How can i implement it that is swiches the buttons (so far i got before) and (the important part) how can i 'refresh' the GUI, so the user can see the chenge visually. Following the code:
import javax.swing.*; import Pieces.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Game extends JFrame {
private static final int width = 8;
private static final int height = 8;
private static Piece clicked;
public static Piece[][] fields = new Piece[width][height];
private JPanel main = new JPanel();
public static void init(JPanel g) {
for (int y = 0; y < fields.length; y++) {
for (int x = 0; x < fields[y].length; x++) {
if (y == 0) {
switch (x) {
case 0,7:
fields[y][x] = new Rook(x,y,1);
break;
case 1,6:
fields[y][x] = new Knight(x,y,1);
break;
case 2,5:
fields[y][x] = new Bishop(x,y,1);
break;
case 3:
fields[y][x] = new Queen(x,y,1);
break;
case 4:
fields[y][x] = new King(x,y,1);
break;
}
}
if (y == 1) fields[y][x] = new Pawn(x, y, 1);
else if (y >= 2 && y <= 5) fields[y][x] = new Empty(x,y,9);
else if (y == 6) fields[y][x] = new Pawn(x, y, 0);
else if(y == 7) {
switch (x) {
case 0,7:
fields[y][x] = new Rook(x,y,0);
break;
case 1,6:
fields[y][x] = new Knight(x,y,0);
break;
case 2,5:
fields[y][x] = new Bishop(x,y,0);
break;
case 3:
fields[y][x] = new Queen(x,y,0);
break;
case 4:
fields[y][x] = new King(x,y,0);
break;
}
}
fields[y][x].addActionListener(e -> {
var p = (Piece) e.getSource();
var pPos = p.getCell();
if(clicked == null) {
clicked = p;
System.out.println(fields[clicked.getCell().y][clicked.getCell().x]);
clicked.setForeground(Color.yellow.darker());
System.out.println("clicked " + pPos);
} else if (pPos == clicked.getCell()) {
clicked.setForeground(Color.white);
System.out.println("deselecting " + pPos);
clicked = null;
} else {
if (clicked.canMoveTo(fields, pPos)) {
fields[p.getCell().y][p.getCell().x] = clicked;
fields[clicked.getCell().y][clicked.getCell().x] = new Empty(clicked.getCell().x, clicked.getCell().y, 9);
System.out.println("moving " + clicked.getCell() + " to " + pPos);
clicked.setForeground(Color.white);
}
else System.out.println("can´t move there, sry");
clicked = null;
}
SwingUtilities.updateComponentTreeUI(g);
});
g.add(fields[y][x]);
}
}
}
public Game() {
main.setBackground(Color.darkGray.darker());
main.setLayout(new GridLayout(8,8));
this.setSize(800,800);
init(main);
this.add(main);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
this.setVisible(true);
}
public static void main(String[] args) {
var g = new Game();
}
}
package Pieces;
import javax.swing.*;
import java.awt.*;
public abstract class Piece extends JButton {
private final int isWhite; //0 is false, 1 is true, 9 is undefined
private Point cell;
public Piece(int x, int y, int isWhite) {
cell = new Point(x, y);
this.isWhite = isWhite;
this.setForeground(Color.white);
this.setOpaque(false);
//this.setContentAreaFilled(false);
}
public Point getCell() {
return cell;
}
public int isWhite() {
return isWhite;
}
public boolean canMoveTo(Piece[][] fields, Point point) {
return true;
}
}
*all the pieces are setup like this
package Pieces;
public class Bishop extends Piece{
public Bishop(int x, int y, int isWhite) {
super(x, y, isWhite);
this.setText("Bishop");
}
}
(yee, this will hopefully be chess sometimes)

Related

Unexpected error with Javafx [duplicate]

This question already has answers here:
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed 5 years ago.
Everybody!
I'm here to annoy you once more)
So, I was trying to make a game, I described some classes:
GameObject - just an object which has X,Y,Sizes,Name and may be drawn and moved:
package pkg3dgraphics;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
public class GameObject {
protected String name;
protected int xCoord,yCoord,mySizeX = 25,mySizeY = 25;
public GameObject(){
}
public GameObject(String newName, int startXCoord, int startYCoord){
setName(newName);
setX(startXCoord);
setY(startYCoord);
}
public void SetSize(int X, int Y){
mySizeX = X;
mySizeY = Y;
}
public int getXsize(){
return mySizeX;
}
public int getYsize(){
return mySizeY;
}
public String getName(){
return name;
}
public void setName(String newName){
name = newName;
}
public int getX(){
return xCoord;
}
public int getY(){
return yCoord;
}
public void setX(int newXCoord){
xCoord = newXCoord;
}
public void setY(int newYCoord){
yCoord = newYCoord;
}
public void moveTo(int X,int Y){
xCoord = X;
yCoord = Y;
}
public void moveBy(int X,int Y){
xCoord +=X;
yCoord +=Y;
}
public void Draw(GraphicsContext currentContext){
currentContext.setFill(Color.GREEN);
currentContext.setStroke(Color.BLUE);
currentContext.fillOval(xCoord,yCoord, mySizeX,mySizeY );
}
}
And I have Shooter extending previous class and here he goes:
package pkg3dgraphics;
public class Shooter extends GameObject {
public Shooter(){
}
public Shooter(String newName, int startXCoord, int startYCoord){
setName(newName);
setX(startXCoord);
setY(startYCoord);
}
public Bullet shoot(String direction){
Bullet newBullet = new Bullet(direction);
return newBullet;
}
}
Also I've got Bullets which are , you know, bullets:
package pkg3dgraphics;
import javafx.scene.paint.Color;
import javafx.scene.canvas.GraphicsContext;
public class Bullet extends GameObject{
String myDirection;
protected int mySpeed = 5,mySizeX = 5,mySizeY = 5;
boolean goNorth,goSouth,goWest,goEast;
protected boolean active = false;
public void setSpeed(int newSpeed){
mySpeed = newSpeed;
}
public void setDirection(String newDirection){
myDirection = newDirection;
active = true;
if ( myDirection == "North" )
goNorth = true;
if ( myDirection == "South" )
goSouth = true;
if ( myDirection == "West" )
goWest = true;
if ( myDirection == "East" )
goEast = true;
}
Bullet(String direction ){
myDirection = direction;
active = true;
if ( myDirection == "North" )
goNorth = true;
if ( myDirection == "South" )
goSouth = true;
if ( myDirection == "West" )
goWest = true;
if ( myDirection == "East" )
goEast = true;
}
public void advance(int W,int H,GraphicsContext gc){
if (xCoord <= W && xCoord >= 0 && yCoord <= H && yCoord >= 0 ) {
if (goNorth) moveBy(0,mySpeed);
if (goSouth) moveBy(0,mySpeed);
if (goWest) moveBy(mySpeed,0);
if (goEast) moveBy(mySpeed,0);
}else{
active = false;
Vanish(gc);
goNorth = false;
goSouth = false;
goWest = false;
goEast = false;
}
}
public void Vanish(GraphicsContext gc){
gc.setFill(Color.WHITE);
}
}
The purpose was to have this guy shooting in a next way:
When I catch pressed button I use precreated inactive bullet and direct it to go in a several direction.
When this bullet crosses a window's border it becomes inactive and stops.
For this I have an array of Bullets.
When user presses shoot key I seek in the array for an inactive Bullet and if there is none I create additional one which follows the path user wanted.
Well, this way to Implement bullets probably is not the best, but I didn't come up with another.
So I compiled my Main Class:
package pkg3dgraphics;
import java.util.Vector;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import static javafx.scene.input.KeyCode.DOWN;
import static javafx.scene.input.KeyCode.LEFT;
import static javafx.scene.input.KeyCode.RIGHT;
import static javafx.scene.input.KeyCode.SHIFT;
import static javafx.scene.input.KeyCode.UP;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
public int WINDOWWIDTH = 600;
public int WINDOWHEIGHT = 400;
boolean goNorth,goSouth,goWest,goEast,running,ShootNorth,ShootSouth,ShootWest,ShootEast;
Shooter player = new Shooter("Player",WINDOWWIDTH/2,WINDOWHEIGHT/2);
Bullet bullets[] = new Bullet[500];
int bulletsSize = 0;
Canvas mainCanvas = new Canvas(WINDOWWIDTH,WINDOWHEIGHT);
GraphicsContext mainContext = mainCanvas.getGraphicsContext2D();
#Override
public void start(Stage primaryStage) {
Group root = new Group();
Canvas mainCanvas = new Canvas(WINDOWWIDTH,WINDOWHEIGHT);
GraphicsContext mainContext = mainCanvas.getGraphicsContext2D();
root.getChildren().add(mainCanvas);
Scene scene = new Scene(root,WINDOWWIDTH,WINDOWHEIGHT);
scene.setOnKeyPressed(new EventHandler<KeyEvent> (){
#Override
public void handle(KeyEvent event){
switch(event.getCode()){
case UP: ShootNorth = true; break;
case DOWN: ShootSouth = true; break;
case LEFT: ShootWest = true; break;
case RIGHT: ShootEast = true; break;
case W: goNorth = true; break;
case S: goSouth = true; break;
case A: goWest = true; break;
case D: goEast = true; break;
}
}
});
scene.setOnKeyReleased(new EventHandler <KeyEvent>(){
#Override
public void handle(KeyEvent event){
switch(event.getCode()){
case UP: ShootNorth = false; break;
case DOWN: ShootSouth = false; break;
case LEFT: ShootWest = false; break;
case RIGHT: ShootEast = false; break;
case W: goNorth = false; break;
case S: goSouth = false; break;
case A: goWest = false; break;
case D: goEast = false; break;
}
}
});
primaryStage.setScene(scene);
primaryStage.show();
AnimationTimer Timer = new AnimationTimer(){
#Override
public void handle (long now){
int dx = 0, dy = 0;
if (goNorth) dy = -1;
if (goSouth) dy = 1;
if (goWest) dx = -1;
if (goEast) dx = 1;
if (running) { dx *= 3; dy *= 3;}
mainContext.clearRect(0,0,WINDOWWIDTH,WINDOWHEIGHT);
player.moveBy(dx, dy);
CheckShoot();
player.Draw(mainContext);
}
};
Timer.start();
}
public void CheckShoot(){
String direction = null;
int count = 0;
if (ShootNorth)
{
direction = "North";
}
if (ShootSouth)
{
direction = "South";
}
if (ShootWest)
{
direction = "West";
}
if (ShootEast)
{
direction = "East";
}
for (int i = 0; i < bulletsSize; i ++ )
{
if (bullets[i].active = false ){
bullets[i].setDirection(direction);
bullets[i].moveTo(player.getX(),player.getY());
break;
}else count ++;
}
if ( count == bulletsSize ) {
bulletsSize++;
bullets[bulletsSize] = player.shoot(direction);
}
}
public void advanceAll(){
for (int i = 0; i < bulletsSize; i ++ )
{
bullets[i].advance(WINDOWWIDTH,WINDOWHEIGHT,mainContext);
}
}
}
Application runs but then I get this problem looped untill I close the application:
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
at pkg3dgraphics.Main.CheckShoot(Main.java:126)
at pkg3dgraphics.Main$3.handle(Main.java:91)
at javafx.animation.AnimationTimer$AnimationTimerReceiver.lambda$handle$483(AnimationTimer.java:57)
at java.security.AccessController.doPrivileged(Native Method)
at javafx.animation.AnimationTimer$AnimationTimerReceiver.handle(AnimationTimer.java:56)
at com.sun.scenario.animation.AbstractMasterTimer.timePulseImpl(AbstractMasterTimer.java:357)
at com.sun.scenario.animation.AbstractMasterTimer$MainLoop.run(AbstractMasterTimer.java:267)
at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:506)
at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:490)
at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$403(QuantumToolkit.java:319)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$147(WinApplication.java:177)
at java.lang.Thread.run(Thread.java:748)
So, that is all I wanted to complain about.
If someone has any idea why these all occure I would be glad and grateful if you shared the secret with me.(And all others)
You seem to have created the array, without creating each members
Bullet bullets[] = new Bullet[500];
Then you iterate through them, while they are nulls
for (int i = 0; i < bulletsSize; i ++ )
{
if (bullets[i].active = false ){
bullets[i].setDirection(direction);
bullets[i].moveTo(player.getX(),player.getY());
break;
}else count ++;
}
bullets[i] = null, and you try to invoke .active on it.
Solution:
Try to check nulls
if (bullets[i] == null) { bullets[i] = new Bullet(); }
if (bullets[i].active = false ){
bullets[i].setDirection(direction);
bullets[i].moveTo(player.getX(),player.getY());
break;
}else count ++;
Read What is NullPointerException

Recursion error in GUI

I am creating a simple 9x9 grid for Minesweeper. One of the primary functions of this game is to have a recursion to check all the sides when the tile clicked has no bombs surrounding it. In the code attached below, I have been able to create a function that checks the upper and left side of the tile. If I add more directions, such as lower and right side, the program will crash and will not properly display the tiles. (Check the method countBorders under the line //MY MAIN PROBLEM)
//displays the main GUI
package Minesweeper4;
public class mainFrame {
public static void main(String[] args) {
new Grid().setVisible(true);
}
}
// the main code
package Minesweeper4;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.*;
public class Grid extends JFrame implements ActionListener {
private JPanel mainGrid;
private JButton button1, button2;
private JButton[][] buttons = new JButton[9][9];
private String[][] mines = new String[9][9];
private ArrayList<ParentSquare> parentSquare = new ArrayList<ParentSquare>();
Random rand = new Random();
NumberSquare numberSquare = new NumberSquare();
MineSquare mineSquare = new MineSquare();
public void addMines() {
for (int j = 0; j < 9; j++) {
for (int k = 0; k < 9; k++) {
mines[j][k] = ".";
}
}
for (int i = 0; i < 3; i++) {
int temp_x = rand.nextInt(9);
int temp_y = rand.nextInt(9);
mines[temp_x][temp_y] = "x";
}
}
public void showMines() {
for (int x = 0; x < 9; x++) {
for (int y = 0; y < 9; y++) {
String temp = mines[x][y];
if (temp.equals("x")) {
System.out.println("X: " + (x + 1) + " Y: " + (y + 1) + " Value: " + temp);
}
}
}
}
public Grid() {
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(500, 500);
this.setTitle("Minesweeper 1.0");
mainGrid = new JPanel();
mainGrid.setLayout(new GridLayout(9, 9));
this.add(mainGrid);
button1 = new JButton("Boop");
button2 = new JButton("Poop");
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
buttons[i][j] = new JButton("");
buttons[i][j].addActionListener(this);
buttons[i][j].setBackground(Color.GRAY);
}
}
for (int k = 0; k < 9; k++) {
for (int l = 0; l < 9; l++) {
mainGrid.add(buttons[k][l]);
}
}
addMines();
showMines();
}
public void countBorders(int x, int y) {
int UL = 0, UU = 0, UR = 0, LL = 0, RR = 0, DL = 0, DD = 0, DR = 0, SUM = 0;
if (x > 0) {
UU = checkTile(x - 1, y);
}
if (y > 0) {
LL = checkTile(x, y - 1);
}
if (y < 8) {
RR = checkTile(x, y + 1);
}
if (x < 8) {
DD = checkTile(x + 1, y);
}
if ((x > 0) && (y > 0)) {
UL = checkTile(x - 1, y - 1);
}
if ((x > 0) && (y < 8)) {
UR = checkTile(x - 1, y + 1);
}
if ((x < 8) && (y > 0)) {
DL = checkTile(x + 1, y - 1);
}
if ((x < 8) && (y < 8)) {
DR = checkTile(x + 1, y + 1);
}
SUM = UL + UU + UR + LL + RR + DL + DD + DR;
printTile(x, y, SUM);
if (SUM == 0) { //MY MAIN PROBLEM
// if ((x > 0) && (y > 0)) {countBorders(x-1, y-1);} //Upper left
if (x > 0) {
countBorders(x - 1, y);
} //Upper
// if ((x > 0) && (y < 8)) {countBorders(x-1, y+1);} //Upper right
if (y > 0) {
countBorders(x, y - 1);
} //Left
// if (y < 8) {countBorders(x, y+1);} //Right
// if ((x < 8) && (y > 0)) {countBorders(x+1, y-1);} //Down Left
// if (x < 8) {countBorders(x+1, y);} //Down
// if ((x < 8) && (y < 8)) {countBorders(x+1, y+1);} //Down Right
}
}
public void printTile(int x, int y, int SUM) {
String text = Integer.toString(SUM);
buttons[x][y].setText(text);
buttons[x][y].setBackground(Color.CYAN);
}
public int checkTile(int x, int y) {
String c = mines[x][y];
if (c.equals("x")) {
return 1;
} else {
return 0;
}
}
public void click(int x, int y) {
String mine = mines[x][y];
if (mine.equals("x")) {
System.out.println("Bomb!!!");
buttons[x][y].setText("!");
buttons[x][y].setBackground(Color.RED);
} else {
countBorders(x, y);
System.out.println("Safe!!!");
// buttons[x][y].setText("√");
// buttons[x][y].setBackground(Color.WHITE);
}
}
#Override
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (e.getSource() == buttons[i][j]) {
System.out.println("Clicked Tile X: " + (i + 1) + " Y: " + (j + 1));
//buttons[i][j].setText("!");
click(i, j);
}
}
}
}
}
Is there a way on how to fix this recursion problem?
Thank you in advance and I'm really trying to learn Java. Have a nice day!
Your error-causing recursion has no stopping logic that I can find, and what you need to do is to somehow check to make sure that a cell hasn't already been counted or pressed before re-counting it. Otherwise the code risks throwing a stackoverflow error. This will require giving the cells being counted some state that would tell you this information, that would tell you if the cell has already been counted.
For an example of a successful program that does this logic, feel free to look at my Swing GUI example, one I created 5 years ago. In this code, I've got a class, MineCellModel, that provides the logic (not the GUI) for a single mine sweeper cell, and the class contains a boolean field, pressed, that is false until the cell is "pressed", either by the user pressing the equivalent button, or recursively in the model's logic. If the cell is pressed, if the boolean is true, the recursion stops with this cell.
You can find the code here: Minesweeper Action Events. It's an old program, and so I apologize for any concepts or code that may be off.
Running the code results in this:
Here's the code present in a single file:
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.SwingConstants;
import javax.swing.event.SwingPropertyChangeSupport;
#SuppressWarnings("serial")
public class MineSweeper {
private JPanel mainPanel = new JPanel();
private MineCellGrid mineCellGrid;
private JButton resetButton = new JButton("Reset");
public MineSweeper(int rows, int cols, int mineTotal) {
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS));
mineCellGrid = new MineCellGrid(rows, cols, mineTotal);
resetButton.setMnemonic(KeyEvent.VK_R);
resetButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
mineCellGrid.reset();
}
});
mainPanel.add(mineCellGrid);
mainPanel.add(new JSeparator());
mainPanel.add(new JPanel() {
{
add(resetButton);
}
});
}
private JPanel getMainPanel() {
return mainPanel;
}
private static void createAndShowUI() {
JFrame frame = new JFrame("MineSweeper");
// frame.getContentPane().add(new MineSweeper(20, 20,
// 44).getMainPanel());
frame.getContentPane().add(new MineSweeper(12, 12, 13).getMainPanel());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowUI();
}
});
}
}
#SuppressWarnings("serial")
class MineCellGrid extends JPanel {
private MineCellGridModel model;
private List<MineCell> mineCells = new ArrayList<MineCell>();
public MineCellGrid(final int maxRows, final int maxCols, int mineNumber) {
model = new MineCellGridModel(maxRows, maxCols, mineNumber);
setLayout(new GridLayout(maxRows, maxCols));
for (int row = 0; row < maxRows; row++) {
for (int col = 0; col < maxCols; col++) {
MineCell mineCell = new MineCell(row, col);
add(mineCell);
mineCells.add(mineCell);
model.add(mineCell.getModel(), row, col);
}
}
reset();
}
public void reset() {
model.reset();
for (MineCell mineCell : mineCells) {
mineCell.reset();
}
}
}
class MineCellGridModel {
private MineCellModel[][] cellModelGrid;
private List<Boolean> mineList = new ArrayList<Boolean>();
private CellModelPropertyChangeListener cellModelPropChangeListener = new CellModelPropertyChangeListener();
private int maxRows;
private int maxCols;
private int mineNumber;
private int buttonsRemaining;
public MineCellGridModel(final int maxRows, final int maxCols, int mineNumber) {
this.maxRows = maxRows;
this.maxCols = maxCols;
this.mineNumber = mineNumber;
for (int i = 0; i < maxRows * maxCols; i++) {
mineList.add((i < mineNumber) ? true : false);
}
cellModelGrid = new MineCellModel[maxRows][maxCols];
buttonsRemaining = (maxRows * maxCols) - mineNumber;
}
public void add(MineCellModel model, int row, int col) {
cellModelGrid[row][col] = model;
model.addPropertyChangeListener(cellModelPropChangeListener);
}
public void reset() {
buttonsRemaining = (maxRows * maxCols) - mineNumber;
// randomize the mine location
Collections.shuffle(mineList);
// reset the model grid and set mines
for (int r = 0; r < cellModelGrid.length; r++) {
for (int c = 0; c < cellModelGrid[r].length; c++) {
cellModelGrid[r][c].reset();
cellModelGrid[r][c].setMined(mineList.get(r * cellModelGrid[r].length + c));
}
}
// advance value property of all neighbors of a mined cell
for (int r = 0; r < cellModelGrid.length; r++) {
for (int c = 0; c < cellModelGrid[r].length; c++) {
if (cellModelGrid[r][c].isMined()) {
int rMin = Math.max(r - 1, 0);
int cMin = Math.max(c - 1, 0);
int rMax = Math.min(r + 1, cellModelGrid.length - 1);
int cMax = Math.min(c + 1, cellModelGrid[r].length - 1);
for (int row2 = rMin; row2 <= rMax; row2++) {
for (int col2 = cMin; col2 <= cMax; col2++) {
cellModelGrid[row2][col2].incrementValue();
}
}
}
}
}
}
private class CellModelPropertyChangeListener implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent evt) {
MineCellModel model = (MineCellModel) evt.getSource();
int row = model.getRow();
int col = model.getCol();
if (evt.getPropertyName().equals(MineCellModel.BUTTON_PRESSED)) {
if (cellModelGrid[row][col].isMineBlown()) {
mineBlown();
} else {
buttonsRemaining--;
if (buttonsRemaining <= 0) {
JOptionPane.showMessageDialog(null, "You've Won!!!", "Congratulations",
JOptionPane.PLAIN_MESSAGE);
}
if (cellModelGrid[row][col].getValue() == 0) {
zeroValuePress(row, col);
}
}
}
}
private void mineBlown() {
for (int r = 0; r < cellModelGrid.length; r++) {
for (int c = 0; c < cellModelGrid[r].length; c++) {
MineCellModel model = cellModelGrid[r][c];
if (model.isMined()) {
model.setMineBlown(true);
}
}
}
}
private void zeroValuePress(int row, int col) {
int rMin = Math.max(row - 1, 0);
int cMin = Math.max(col - 1, 0);
int rMax = Math.min(row + 1, cellModelGrid.length - 1);
int cMax = Math.min(col + 1, cellModelGrid[row].length - 1);
for (int row2 = rMin; row2 <= rMax; row2++) {
for (int col2 = cMin; col2 <= cMax; col2++) {
cellModelGrid[row2][col2].pressedAction();
}
}
}
}
}
#SuppressWarnings("serial")
class MineCell extends JPanel {
private static final String LABEL = "label";
private static final String BUTTON = "button";
private static final int PS_WIDTH = 24;
private static final int PS_HEIGHT = PS_WIDTH;
private static final float LABEL_FONT_SIZE = (float) (24 * PS_WIDTH) / 30f;
private static final float BUTTON_FONT_SIZE = (float) (14 * PS_WIDTH) / 30f;
private JButton button = new JButton();
private JLabel label = new JLabel(" ", SwingConstants.CENTER);
private CardLayout cardLayout = new CardLayout();
private MineCellModel model;
public MineCell(final boolean mined, int row, int col) {
model = new MineCellModel(mined, row, col);
model.addPropertyChangeListener(new MyPCListener());
label.setFont(label.getFont().deriveFont(Font.BOLD, LABEL_FONT_SIZE));
button.setFont(button.getFont().deriveFont(Font.PLAIN, BUTTON_FONT_SIZE));
button.setMargin(new Insets(1, 1, 1, 1));
setLayout(cardLayout);
add(button, BUTTON);
add(label, LABEL);
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
pressedAction();
}
});
button.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON3) {
model.upDateButtonFlag();
}
}
});
}
public MineCell(int row, int col) {
this(false, row, col);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PS_WIDTH, PS_HEIGHT);
}
public void pressedAction() {
if (model.isFlagged()) {
return;
}
model.pressedAction();
}
public void showCard(String cardConstant) {
cardLayout.show(this, cardConstant);
}
// TODO: have this change the button's icon
public void setFlag(boolean flag) {
if (flag) {
button.setBackground(Color.yellow);
button.setForeground(Color.red);
button.setText("f");
} else {
button.setBackground(null);
button.setForeground(null);
button.setText("");
}
}
private void setMineBlown(boolean mineBlown) {
if (mineBlown) {
label.setBackground(Color.red);
label.setOpaque(true);
showCard(LABEL);
} else {
label.setBackground(null);
}
}
public MineCellModel getModel() {
return model;
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
model.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
model.removePropertyChangeListener(listener);
}
private class MyPCListener implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent evt) {
String propName = evt.getPropertyName();
if (propName.equals(MineCellModel.MINE_BLOWN)) {
setMineBlown(true);
} else if (propName.equals(MineCellModel.FLAG_CHANGE)) {
setFlag(model.isFlagged());
} else if (propName.equals(MineCellModel.BUTTON_PRESSED)) {
if (model.isMineBlown()) {
setMineBlown(true);
} else {
String labelText = (model.getValue() == 0) ? "" : String.valueOf(model
.getValue());
label.setText(labelText);
}
showCard(LABEL);
}
}
}
public void reset() {
setFlag(false);
setMineBlown(false);
showCard(BUTTON);
label.setText("");
}
}
class MineCellModel {
public static final String FLAG_CHANGE = "Flag Change";
public static final String BUTTON_PRESSED = "Button Pressed";
public static final String MINE_BLOWN = "Mine Blown";
private int row;
private int col;
private int value = 0;
private boolean mined = false;;
private boolean flagged = false;
private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
private boolean pressed = false;
private boolean mineBlown = false;
public MineCellModel(boolean mined, int row, int col) {
this.mined = mined;
this.row = row;
this.col = col;
}
public void incrementValue() {
int temp = value + 1;
setValue(temp);
}
public void setValue(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void setMineBlown(boolean mineBlown) {
this.mineBlown = mineBlown;
PropertyChangeEvent evt = new PropertyChangeEvent(this, MINE_BLOWN, false, true);
pcSupport.firePropertyChange(evt);
}
public boolean isMineBlown() {
return mineBlown;
}
public void setMined(boolean mined) {
this.mined = mined;
}
public void setFlagged(boolean flagged) {
this.flagged = flagged;
}
public int getRow() {
return row;
}
public int getCol() {
return col;
}
public boolean isMined() {
return mined;
}
public boolean isFlagged() {
return flagged;
}
public void pressedAction() {
if (pressed) {
return;
}
pressed = true;
if (mined) {
setMineBlown(true);
}
PropertyChangeEvent evt = new PropertyChangeEvent(this, BUTTON_PRESSED, -1, value);
pcSupport.firePropertyChange(evt);
}
public void upDateButtonFlag() {
boolean oldValue = flagged;
setFlagged(!flagged);
PropertyChangeEvent evt = new PropertyChangeEvent(this, FLAG_CHANGE, oldValue, flagged);
pcSupport.firePropertyChange(evt);
}
public void reset() {
mined = false;
flagged = false;
pressed = false;
mineBlown = false;
value = 0;
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcSupport.removePropertyChangeListener(listener);
}
}
Edit Regarding Recursion
My code uses recursion, but with a level of indirection, since it is based on a Model-View-Controller type of design pattern, and the recursion is within the notification of listeners. Note that each GUI MineCell object holds its own MineCellModel object, the latter holds the MineCell's state. When a GUI JButton held within the MineCell object is pressed, its ActionListener calls the same class's pressed() method:
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
pressedAction();
}
});
This method first checks the corresponding MineCellModel to see if it has been "flagged", if a boolean called flagged is true. If so, this means that the user has right-clicked on the button, and it is not active, and so the method returns. Otherwise the MineCellModel's pressedAction() method is called,
public void pressedAction() {
if (model.isFlagged()) {
return;
}
model.pressedAction();
}
and here is where the recursion starts, and it does so through an Observer Design Pattern:
// within MineCellModel
public void pressedAction() {
if (pressed) {
// if the button's already been pressed -- return, do nothing
return;
}
// otherwise make pressed true
pressed = true;
// if we've hit a mine -- blow it!
if (mined) {
setMineBlown(true);
}
// *** Here's the key *** notify all listeners that this button has been pressed
PropertyChangeEvent evt = new PropertyChangeEvent(this, BUTTON_PRESSED, -1, value);
pcSupport.firePropertyChange(evt);
}
The two lines of code on the bottom notify any listeners to this model that its BUTTON_PRESSED state has been changed, and it sends the MineCellModel's value to all listeners. The value int is key as its the number of neighbors that have mines. So what listens to the MineCellModel? Well, one key object is the MineCellGridModel, the model that represents the state of the entire grid. It has a CellModelPropertyChangeListener class that does the actual listening, and within this class is the following code:
private class CellModelPropertyChangeListener implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent evt) {
// first get the MineCellModel for the cell that triggered this notification
MineCellModel model = (MineCellModel) evt.getSource();
int row = model.getRow();
int col = model.getCol();
// if the event is a button pressed event
if (evt.getPropertyName().equals(MineCellModel.BUTTON_PRESSED)) {
// first check if a mine was hit, and if so, call mineBlown()
if (cellModelGrid[row][col].isMineBlown()) {
mineBlown(); // this method iterates through all cells and blows all mines
} else {
// here we check for a winner
buttonsRemaining--;
if (buttonsRemaining <= 0) {
JOptionPane.showMessageDialog(null, "You've Won!!!", "Congratulations",
JOptionPane.PLAIN_MESSAGE);
}
// here is the key spot -- if cell's value is 0, call the zeroValuePress method
if (cellModelGrid[row][col].getValue() == 0) {
zeroValuePress(row, col);
}
}
}
}
private void mineBlown() {
// ... code to blow all the un-blown mines
}
// this code is called if a button pressed has 0 value -- no mine neighbors
private void zeroValuePress(int row, int col) {
// find the boundaries of the neighbors
int rMin = Math.max(row - 1, 0); // check for the top edge
int cMin = Math.max(col - 1, 0); // check for the left edge
int rMax = Math.min(row + 1, cellModelGrid.length - 1); // check for the bottom edge
int cMax = Math.min(col + 1, cellModelGrid[row].length - 1); // check for right edge
// iterate through the neighbors
for (int row2 = rMin; row2 <= rMax; row2++) {
for (int col2 = cMin; col2 <= cMax; col2++) {
// *** Here's the recursion ***
// call pressedAction on all the neighbors
cellModelGrid[row2][col2].pressedAction();
}
}
}
}
So the key method in the listener above is the zeroValuePress(...) method. It first finds the boundaries of the neighbors around the current mine cell, using Math.min(...) and Math.max(...) to be careful not to go beyond the right, left, or top or bottom boundaries of the grid. It then iterates through the cell neighbors calling pressedAction() on each one of the neighbors MineCellModels held by this grid. As you know from above, the pressedAction() method will check if the cell has already been pressed, and if not, changes its state, which then notifies this same listener, resulting in recursion.
One of the primary functions of this game is to have a recursion to check all the sides when the tile clicked has no bombs surrounding it.
Looks like you are stucked on the part where you need to update the cell with number according to the number of bombs surrounding it.
These are the things for you to take note:
To update the numbers on the cells, there is no need to use recursion. The only part I used recursion is when user clicks on a cell with value == 0(stepped on an empty grid).
Checking all 8 directions can be done easily without writing large number of if-conditions. All you need is a pair of nested for-loop. Just traverse the 3x3 grid like a 2D array (see diagram below for illustration).
In the loop, set conditions to ensure you are within bounds (of the 3x3 matrix) before reading current grid's value (see code below).
To traverse the 3x3 matrix as shown in the diagram, we can use a pair of nested loops:
for(int x=(coordX-1); x<=(coordX+1); x++)
for(int y=(coordY-1); y<=(coordY+1); y++)
if(x!=-1 && y!= -1 && x! = ROWS && y! = COLS && map[x][y] != 'B')
if(map[x][y] == '.')
map[x][y] = '1';
else
map[x][y] += 1;
The if-condition prevents working on array element which is out of bounds.

How to add icon to JFrame [duplicate]

This question already has answers here:
How to change JFrame icon [duplicate]
(8 answers)
Closed 6 years ago.
How do I add an icon to this snake game and where do I put it, also how do I increase speed of the game after so many points? The code below is the class in which I believe these two pieces of code should go.
import java.awt.BorderLayout;
import java.awt.Point;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
public class SnakeGame extends JFrame {
private static final long FRAME_TIME = 1000L / 50L;
private static final int MIN_SNAKE_LENGTH = 5;
private static final int MAX_DIRECTIONS = 3;
private BoardPanel board;
private SidePanel side;
private Random random;
private Clock logicTimer;
private boolean isNewGame;
private boolean isGameOver;
private boolean isPaused;
private LinkedList<Point> snake;
private LinkedList<Direction> directions;
private int score;
private int foodsEaten;
private int nextFoodScore;
private SnakeGame() {
super("Snake");
setLayout(new BorderLayout());
setDefaultCloseOperation(EXIT_ON_CLOSE);
setResizable(false);
this.board = new BoardPanel(this);
this.side = new SidePanel(this);
add(board, BorderLayout.CENTER);
add(side, BorderLayout.EAST);
addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
switch(e.getKeyCode()) {
case KeyEvent.VK_W:
case KeyEvent.VK_UP:
if(!isPaused && !isGameOver) {
if(directions.size() < MAX_DIRECTIONS) {
Direction last = directions.peekLast();
if(last != Direction.South && last != Direction.North) {
directions.addLast(Direction.North);
}
}
}
break;
case KeyEvent.VK_S:
case KeyEvent.VK_DOWN:
if(!isPaused && !isGameOver) {
if(directions.size() < MAX_DIRECTIONS) {
Direction last = directions.peekLast();
if(last != Direction.North && last != Direction.South) {
directions.addLast(Direction.South);
}
}
}
break;
case KeyEvent.VK_A:
case KeyEvent.VK_LEFT:
if(!isPaused && !isGameOver) {
if(directions.size() < MAX_DIRECTIONS) {
Direction last = directions.peekLast();
if(last != Direction.East && last != Direction.West) {
directions.addLast(Direction.West);
}
}
}
break;
case KeyEvent.VK_D:
case KeyEvent.VK_RIGHT:
if(!isPaused && !isGameOver) {
if(directions.size() < MAX_DIRECTIONS) {
Direction last = directions.peekLast();
if(last != Direction.West && last != Direction.East) {
directions.addLast(Direction.East);
}
}
}
break;
case KeyEvent.VK_P:
if(!isGameOver) {
isPaused = !isPaused;
logicTimer.setPaused(isPaused);
}
break;
case KeyEvent.VK_ENTER:
if(isNewGame || isGameOver) {
resetGame();
}
break;
}
}
});
pack();
setLocationRelativeTo(null);
setVisible(true);
}
private void startGame() {
this.random = new Random();
this.snake = new LinkedList<>();
this.directions = new LinkedList<>();
this.logicTimer = new Clock(10.0f);
//////////////////////////////////////////////////////////////////////////////////////////////////
this.isNewGame = true;
logicTimer.setPaused(true);
while(true) {
long start = System.nanoTime();
logicTimer.update();
if(logicTimer.hasElapsedCycle()) {
updateGame();
}
board.repaint();
side.repaint();
long delta = (System.nanoTime() - start) / 1000000L;
if(delta < FRAME_TIME) {
try {
Thread.sleep(FRAME_TIME - delta);
} catch(Exception e) {
e.printStackTrace();
}
}
}
}
private void updateGame() {
TileType collision = updateSnake();
if(collision == TileType.Food) {
foodsEaten++;
score += nextFoodScore;
spawnFood();
} else if(collision == TileType.SnakeBody) {
isGameOver = true;
logicTimer.setPaused(true);
} else if(nextFoodScore > 10) {
}
}
private TileType updateSnake() {
Direction direction = directions.peekFirst();
Point head = new Point(snake.peekFirst());
switch(direction) {
case North:
head.y--;
break;
case South:
head.y++;
break;
case West:
head.x--;
break;
case East:
head.x++;
break;
}
if(head.x < 0 || head.x >= BoardPanel.COL_COUNT || head.y < 0 || head.y >= BoardPanel.ROW_COUNT) {
return TileType.SnakeBody;
}
TileType old = board.getTile(head.x, head.y);
if(old != TileType.Food && snake.size() > MIN_SNAKE_LENGTH) {
Point tail = snake.removeLast();
board.setTile(tail, null);
old = board.getTile(head.x, head.y);
}
if(old != TileType.SnakeBody) {
board.setTile(snake.peekFirst(), TileType.SnakeBody);
snake.push(head);
board.setTile(head, TileType.SnakeHead);
if(directions.size() > 1) {
directions.poll();
}
}
return old;
}
private void resetGame() {
this.score = 0;
this.foodsEaten = 0;
this.isNewGame = false;
this.isGameOver = false;
Point head = new Point(BoardPanel.COL_COUNT / 2, BoardPanel.ROW_COUNT / 2);
snake.clear();
snake.add(head);
board.clearBoard();
board.setTile(head, TileType.SnakeHead);
directions.clear();
directions.add(Direction.North);
logicTimer.reset();
spawnFood();
}
public boolean isNewGame() {
return isNewGame;
}
public boolean isGameOver() {
return isGameOver;
}
public boolean isPaused() {
return isPaused;
}
private void spawnFood() {
this.nextFoodScore = 10;
int index = random.nextInt(BoardPanel.COL_COUNT * BoardPanel.ROW_COUNT - snake.size());
int freeFound = -1;
for(int x = 0; x < BoardPanel.COL_COUNT; x++) {
for(int y = 0; y < BoardPanel.ROW_COUNT; y++) {
TileType type = board.getTile(x, y);
if(type == null || type == TileType.Food) {
if(++freeFound == index) {
board.setTile(x, y, TileType.Food);
break;
}
}
}
}
}
public int getScore() {
return score;
}
public int getFoodsEaten() {
return foodsEaten;
}
public int getNextFoodScore() {
return nextFoodScore;
}
public Direction getDirection() {
return directions.peek();
}
public static void main(String[] args) {
SnakeGame snake = new SnakeGame();
snake.startGame();
}
}
Create a new ImageIcon object like this:
ImageIcon img = new ImageIcon(pathToFileOnDisk);
Then set it to your JFrame with setIconImage():
myFrame.setIconImage(img.getImage());
Also checkout setIconImages() which takes a List instead.
How to change JFrame icon
It 's not my answer !!!!

Making a simple Mine Sweeper game in Java

I'm working on creating a simple mine sweeper game in java using JButtons. So far I have a code that creates a 20x20 grid of JButtons, but I am unsure of how I can get my bombs randomly assigned to multimple JButtons durring the game.
Here is what I have written so far:
MineSweeper Class:
import javax.swing.*;
import java.awt.GridLayout;
public class MineSweeper extends JFrame {
JPanel p = new JPanel();
bombButton points[][]= new bombButton[20][20];
public static void main(String args[]){
new MineSweeper();
}
public MineSweeper(){
super("Mine Sweeper Version: Beta");
setSize(400,400);
setResizable(false);
setDefaultCloseOperation(EXIT_ON_CLOSE);
p.setLayout(new GridLayout(20,20));
int y=0;
int counter=0;
while(counter<20){
for(int x=0;x<20;x++){
points[x][y] = new bombButton();
p.add(points[x][y]);
}
y++;
counter++;
}
add(p);
setVisible(true);
}
}
bombButton Class:
import javax.swing.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.net.URL;
public class bombButton extends JButton implements ActionListener {
ImageIcon Bomb,zero,one,two,three,four,five,six,seven,eight;
public bombButton(){
URL imageBomb = getClass().getResource("Bomb.png");
Bomb= new ImageIcon(imageBomb);
URL imageZero = getClass().getResource("0.jpg");
zero= new ImageIcon(imageZero);
URL imageOne = getClass().getResource("1.jpg");
one= new ImageIcon(imageOne);
URL imageTwo = getClass().getResource("2.jpg");
two= new ImageIcon(imageTwo);
URL imageThree = getClass().getResource("3.jpg");
three= new ImageIcon(imageThree);
URL imageFour = getClass().getResource("4.jpg");
four= new ImageIcon(imageFour);
URL imageFive = getClass().getResource("5.jpg");
five= new ImageIcon(imageFive);
URL imageSix = getClass().getResource("6.jpg");
six= new ImageIcon(imageSix);
URL imageSeven = getClass().getResource("7.jpg");
seven= new ImageIcon(imageSeven);
URL imageEight = getClass().getResource("8.jpg");
eight= new ImageIcon(imageEight);
this.addActionListener(this);
}
public void actionPerformed(ActionEvent e){
switch(){
case 0:
setIcon(null);
break;
case 1:
setIcon(Bomb);
break;
case 2:
setIcon(one);
break;
case 3:
setIcon(two);
break;
case 4:
setIcon(three);
break;
case 5:
setIcon(four);
break;
case 6:
setIcon(five);
break;
case 7:
setIcon(six);
break;
case 8:
setIcon(seven);
break;
case 9:
setIcon(eight);
break;
}
}
int randomWithRange(int min, int max)
{
int range = Math.abs(max - min) + 1;
return (int)(Math.random() * range) + (min <= max ? min : max);
}
}
As you can see I already have a randomizer set up, I just don't know how I should implement it. Should I use (X,Y) cordinates? How do I assign my bombs to random JButtons?
Thnak you to all in advance!
Create an ArrayList<JButton>, fill it with all your buttons, call Collections.shuffle(..) on the list, and then select the first N buttons to add mines to.
Having said this, my real recommendation is to chuck all this and go the MVC route where your data model, including where the mines are located, and your GUI are completely distinct.
here are some of my prior musings on this problem from 2011.
here are my Miseweeper Clone
package MWeeper;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
public class MMWeeper extends JFrame implements Runnable {
private JFrame mainFrame ;
private JPanel mainPanel;
private int boardX = 15;
private int boardY = 15;
private int bombs = 35;
private int bombsMarked;
private int cleanFields;
private int seconds;
private boolean gameOver = false;
private Map<Integer, Map<Integer, mweeperField>> boardMap;
private Map<Integer,position> bombMap;
private JPanel boardPanel;
private JPanel headPanel;
private JTextField bombsField;
#Override
public void run() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
mainFrame = new JFrame("MMWEEEEEEPER");
int w = Toolkit.getDefaultToolkit().getScreenSize().width;
int h = Toolkit.getDefaultToolkit().getScreenSize().height;
mainFrame.setPreferredSize(new Dimension(350,390));
mainFrame.setResizable(true);
mainPanel = new JPanel(new BorderLayout());
init();
//setContent();
setPanel();
mainFrame.add(mainPanel);
mainFrame.setContentPane(mainFrame.getContentPane());
mainFrame.pack();
mainFrame.setLocationRelativeTo(null);
mainFrame.setVisible(true);
}
});
}
private void init() {
bombMap = new HashMap<Integer, MMWeeper.position>();
boardMap = new HashMap<Integer, Map<Integer,mweeperField>>();
bombsMarked = 0;
cleanFields = (boardX * boardY) - bombs;
seconds = 0;
for(int i = 1; i<= boardX; i++) {
boardMap.put(i, new HashMap<Integer, mweeperField>());
for(int j = 1; j <= boardY; j++) {
boardMap.get(i).put(j, new mweeperField(i, j ));
}
}
placeBombs();
}
private boolean placeBombs() {
Random pX = new Random();
Random pY = new Random();
int bombCount = 0;
//while( bombMap.size() < bombs ) {
while(bombCount < bombs) {
int x = (1 + pX.nextInt( boardX ) );
int y = (1 + pY.nextInt( boardY ) );
if(!boardMap.get(x).get(y).isBomb() ) {
boardMap.get(x).get(y).setBomb();
bombCount++;
bombMap.put(bombCount, new position(x, y));
}
}
return true;
}
private void setPanel() {
mainPanel.add(head(), BorderLayout.PAGE_START);
mainPanel.add(board(), BorderLayout.CENTER);
}
private JPanel head() {
headPanel = new JPanel(new BorderLayout());
bombsField = new JTextField(6);
bombsField.setEditable(true);
bombsField.setText( String.valueOf(bombs));
JButton start = new JButton("Start");
start.addActionListener( new mweeperAction(GameActions.START) );
headPanel.add(bombsField, BorderLayout.LINE_START);
headPanel.add(start, BorderLayout.LINE_END);
return headPanel;
}
private JPanel board() {
boardPanel = new JPanel();
GridLayout gLayout = new GridLayout(15, 15, 0, 0 );
boardPanel.setLayout(gLayout);
for( Integer x : boardMap.keySet()) {
for(Integer y : boardMap.get(x).keySet()) {
boardPanel.add( boardMap.get(x).get(y).getButton() );
}
}
return boardPanel;
}
private void gameOver() {
this.gameOver = true;
for( Integer x : boardMap.keySet()) {
for(Integer y : boardMap.get(x).keySet()) {
boardMap.get(x).get(y).trigger();
}
}
}
public class mweeperField implements mousePerformer {
private position pos;
private FieldStatus status = FieldStatus.HIDE_UNMARKED;
private boolean isBomb = false;
private int bombsAroundMe = 0;
private JButton but;
private boolean isTriggered = false;
public mweeperField( int x, int y ) {
this.pos = new position(x, y);
init();
}
public mweeperField( position p ) {
this.pos = p;
init();
}
public void resetField() {
status = FieldStatus.HIDE_UNMARKED;
isBomb = false;
bombsAroundMe = 0;
isTriggered = false;
but.setFont(new Font("Arial", Font.BOLD, 13));
but.setBackground(Color.LIGHT_GRAY);
but.setText(" ");
but.setEnabled(true);
}
public void setBomb() {
this.isBomb = true;
}
public boolean isBomb() {
return isBomb;
}
private void init() {
but = new JButton(" ");
but.setMaximumSize(new Dimension(16, 16));
but.setMinimumSize(new Dimension(16, 16));
but.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY, 1));
but.setMargin(new Insets(0, 0, 0, 0));
but.setBackground(Color.LIGHT_GRAY);
but.addMouseListener(new mweeperMouseListener(this.pos, this));
but.setFont(new Font("Arial", Font.BOLD, 14));
}
private void setButton() {
switch(status) {
case HIDE_MARKED:
//but.setForeground( new Color(224, 124, 168) );
but.setForeground( Color.RED);
but.setText("#");
but.setEnabled(true);
break;
case HIDE_UNMARKED:
but.setForeground(Color.BLACK);
but.setText(" ");
but.setEnabled(true);
break;
case OPEN_NOBOMB:
switch(this.bombsAroundMe) {
case 1:
case 2:
but.setForeground(Color.BLUE);
break;
case 3:
case 4:
but.setForeground(Color.MAGENTA);
break;
case 5:
case 6:
but.setForeground(Color.RED);
break;
case 7:
case 8:
but.setForeground(Color.PINK);
break;
}
String butText = " ";
if(this.bombsAroundMe > 0) {
butText = String.valueOf(this.bombsAroundMe);
}
but.setEnabled(false);
but.setText( butText );
break;
case OPEN_BOMB: // GAME OVER
but.setForeground(Color.BLACK);
but.setFont(new Font("Arial", Font.BOLD, 20));
but.setVerticalAlignment(SwingConstants.CENTER);
but.setHorizontalAlignment(SwingConstants.CENTER);
but.setText("*");
break;
}
// but.setEnabled(false);
but.validate();
but.repaint();
boardPanel.validate();
boardPanel.repaint();
mainPanel.repaint();
}
public JButton getButton() {
return but;
}
/*
+-----+-----+-----+
| x-1 | x | x+1 |
| y-1 | y-1 | y-1 |
+-----+-----+-----+
| x-1 | x/y | x+1 |
| y | | y |
+-----+-----+-----+
| x-1 | x | x+1 |
| y+1 | y+1 | y+1 |
+-----+-----+-----+
*/
private void scan() {
bombsAroundMe = 0;
for(Integer k : pos.posAroundMe.keySet() ) {
position p2 = pos.posAroundMe.get(k);
if(boardMap.get(p2.x).get(p2.y).isBomb()) {
bombsAroundMe++;
}
}
}
public void trigger() {
if(!isTriggered) {
isTriggered = true;
if(!isBomb) {
status = FieldStatus.OPEN_NOBOMB;
}else {
status = FieldStatus.OPEN_BOMB;
}
scan();
setButton();
if(bombsAroundMe == 0) {
// um mich herum triggern
for(Integer k : pos.posAroundMe.keySet() ) {
position p2 = pos.posAroundMe.get(k);
boardMap.get(p2.x).get(p2.y).trigger();
}
}
}
}
#Override
public void doClick(MouseEvent e, position pos) {
switch(e.getButton()) {
case 1: //Links Klick = triggern wenn nich markiert und hide
if(this.status.equals(FieldStatus.HIDE_UNMARKED)){
if(this.isBomb) {
// GAME OVER =8-(
status = FieldStatus.OPEN_BOMB;
but.setBackground(Color.RED);
gameOver();
}else {
trigger();
}
}
break;
case 3: // Rechtsklick
if(this.status.equals(FieldStatus.HIDE_UNMARKED)) {
// Mark Field
this.status = FieldStatus.HIDE_MARKED;
bombsMarked++;
}else {
// Umark Field
this.status = FieldStatus.HIDE_UNMARKED;
bombsMarked--;
}
setButton();
break;
}
}
}
public class position {
public int x = 0;
public int y = 0;
public Map<Integer, position> posAroundMe;
public position(int x, int y) {
this.x = x;
this.y = y;
posAroundMe = new HashMap<Integer, MMWeeper.position>();
setPosAroundMe();
}
public position(int x, int y, boolean setPos) {
posAroundMe = new HashMap<Integer, MMWeeper.position>();
this.x = x;
this.y = y;
}
private void setPosAroundMe() {
int c = 1;
for(int x2 = (x-1); x2 <= (x+1); x2++) {
for(int y2 = (y-1); y2 <= (y+1); y2++) {
if( ((x2 != x) || (y2 != y)) && ( x2>0 && x2<=boardX && y2>0 && y2<=boardY ) ){
posAroundMe.put(c++, new position(x2, y2, false));
}
}
}
}
}
public enum FieldStatus{
HIDE_UNMARKED,
HIDE_MARKED,
OPEN_NOBOMB,
OPEN_BOMB;
}
public enum GameActions{
START;
}
public class mweeperAction extends AbstractAction{
private GameActions gameAction;
public mweeperAction(GameActions ga ) {
this.gameAction = ga;
}
#Override
public void actionPerformed(ActionEvent ae) {
switch(gameAction) {
case START:
for( Integer x : boardMap.keySet()) {
for(Integer y : boardMap.get(x).keySet()) {
boardMap.get(x).get(y).resetField();;
boardMap.get(x).get(y).getButton().validate();
boardMap.get(x).get(y).getButton().repaint();;
}
}
int newBombCount = Integer.valueOf(bombsField.getText()) ;
if(newBombCount < 10) {
newBombCount = 10;
}
if(newBombCount > ((boardX * 2) + 20 ) ){
newBombCount = ((boardX * 2) + 20 );
}
bombs = newBombCount;
bombsField.setText(String.valueOf(bombs) );
placeBombs();
boardPanel.validate();
boardPanel.repaint();
mainPanel.repaint();
break;
}
}
}
public class mweeperMouseListener implements MouseListener{
private position pos;
mousePerformer performer;
public mweeperMouseListener(position pos, mousePerformer acPerf) {
this.pos = pos;
this.performer = acPerf;
}
#Override
public void mouseClicked(MouseEvent e) {
this.performer.doClick(e , pos );
}
#Override
public void mouseEntered(MouseEvent e) {}
#Override
public void mouseExited(MouseEvent e) {}
#Override
public void mousePressed(MouseEvent e) {}
#Override
public void mouseReleased(MouseEvent e) {}
}
public interface mousePerformer{
public void doClick(MouseEvent e, position pos );
}
public interface actionPerformer{
public void doAction(ActionEvent ae, GameActions ga );
}
}

Java Game Loop: Frame Jumps

I've posted a video on Youtube, where you can see the problem. I've slowed the video down, so you really can see the jumps.
LINK: https://www.youtube.com/watch?v=17Wftj2-MRM&feature=youtu.be
The problem is most visible in the last part of the video. You can clearly see the frame jumps, it's like it skipped a frame or two..
I have none problems with lag or performance, as the computer i'm running the games on got the latest tech..
I thought it may had something to do with the game loop, like a rounding error or something, but I can't seem to find any bug in the code..
I've looked for a similar problem online, but can't find it..
So here's my code
The GUI
import java.awt.Container;
import java.awt.Graphics2D;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
public class Gui extends JFrame implements Runnable {
private static final long serialVersionUID = 1L;
public static final int FRAMES_PR_SEC = 60;
public static final int TIME_PR_FRAME = 1000 / FRAMES_PR_SEC;
public static final int MARGIN_X = 3;
public static final int MARGIN_Y = 32;
public static final int WIDTH = 800;
public static final int HEIGHT = 600;
public static int TILE_SIZE = 32;
public static int SPEED = 5;
public static void main(final String[] args) {
new Gui();
}
private Game _game;
private Container _cp;
private BufferedImage _img;
private Graphics2D _g;
public Gui() {
super("Tile Based Game");
setSize(WIDTH + MARGIN_X + 3, HEIGHT + MARGIN_Y - 4);
setResizable(false);
setVisible(true);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
_cp = getContentPane();
_game = new Game();
newGraphics();
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(final MouseEvent arg0) {
_game.mP(arg0.getX() - MARGIN_X, arg0.getY() - MARGIN_Y);
}
#Override
public void mouseReleased(final MouseEvent arg0) {
_game.mR(arg0.getX() - MARGIN_X, arg0.getY() - MARGIN_Y);
}
});
addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(final KeyEvent arg0) {
_game.keyP(arg0.getKeyCode());
}
#Override
public void keyReleased(final KeyEvent arg0) {
_game.keyR(arg0.getKeyCode());
}
});
new Thread(this).start();
}
private void newGraphics() {
_img = getGraphicsConfiguration().createCompatibleImage(WIDTH, HEIGHT);
_g = _img.createGraphics();
}
#Override
public void run() {
while (true) {
long before = System.currentTimeMillis();
_game.update();
newGraphics();
_game.draw(_g);
_cp.getGraphics().drawImage(_img, 0, 0, null);
long after = System.currentTimeMillis();
long sleep = TIME_PR_FRAME - (after - before);
System.out.println(sleep);
if (sleep < 5) {
sleep = 5;
}
try {
Thread.sleep(sleep);
} catch (InterruptedException e) {
}
}
}
}
I suppose that everything you will need is in the GUI class, but I've included the other classes in case you need anything from them.
The Game class
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
public class Game {
private Map[] _maps;
private String[] _mapNames;
private int _currentMap;
private boolean _r;
private boolean _l;
private boolean _u;
private boolean _d;
public Game() {
_mapNames = new String[0];
_maps = new Map[0];
addMap("test.map");
}
public void addMap(final String s) {
String[] stringTemp = _mapNames;
_mapNames = new String[stringTemp.length + 1];
for (int i = 0; i < stringTemp.length; i++) {
_mapNames[i] = stringTemp[i];
}
_mapNames[_mapNames.length - 1] = s.replace(".map", "");
Map[] mapTemp = _maps;
_maps = new Map[mapTemp.length + 1];
for (int i = 0; i < mapTemp.length; i++) {
_maps[i] = mapTemp[i];
}
_maps[_maps.length - 1] = new Map(s);
}
public void draw(final Graphics2D _g) {
_maps[_currentMap].draw(_g);
_g.setColor(Color.WHITE);
_g.drawString(_mapNames[_currentMap], 5, 595);
}
public void keyP(final int keyCode) {
if (keyCode == KeyEvent.VK_RIGHT) {
_r = true;
}
if (keyCode == KeyEvent.VK_LEFT) {
_l = true;
}
if (keyCode == KeyEvent.VK_UP) {
_u = true;
}
if (keyCode == KeyEvent.VK_DOWN) {
_d = true;
}
}
public void keyR(final int keyCode) {
if (keyCode == KeyEvent.VK_SPACE) {
if (_currentMap < _maps.length - 1) {
_currentMap++;
} else {
_currentMap = 0;
}
}
if (keyCode == KeyEvent.VK_RIGHT) {
_r = false;
}
if (keyCode == KeyEvent.VK_LEFT) {
_l = false;
}
if (keyCode == KeyEvent.VK_UP) {
_u = false;
}
if (keyCode == KeyEvent.VK_DOWN) {
_d = false;
}
}
public void mP(final int i, final int j) {
}
public void mR(final int i, final int j) {
}
public void update() {
if (_r) {
_maps[_currentMap].incOffsetX(Gui.SPEED);
}
if (_l) {
_maps[_currentMap].decOffsetX(Gui.SPEED);
}
if (_u) {
_maps[_currentMap].decOffsetY(Gui.SPEED);
}
if (_d) {
_maps[_currentMap].incOffsetY(Gui.SPEED);
}
_maps[_currentMap].update();
}
}
And finally the Map class (handles loading of .map files and such)
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import javax.imageio.ImageIO;
public class Map {
private class Tile {
private Image _img;
private int _x;
private int _y;
public Tile(final Image img) {
_img = img;
}
public Image getImg() {
return _img;
}
public int getX() {
return _x;
}
public int getY() {
return _y;
}
public void setLoc(final int x, final int y) {
_x = x * Gui.TILE_SIZE;
_y = y * Gui.TILE_SIZE;
}
}
private int _offsetX;
private int _offsetY;
private String[][] _map;
private Tile[][] _tileMap;
private BufferedImage _terrain;
private Color _waterColor;
public Map(final String s) {
_terrain = loadImg("terrain.png");
_waterColor = new Color(21, 108, 153);
_map = loadMap(s);
_offsetX = _map[0].length * Gui.TILE_SIZE / 2 - Gui.WIDTH / 2;
_offsetY = _map.length * Gui.TILE_SIZE / 2 - Gui.HEIGHT / 2;
_tileMap = new Tile[_map.length][_map[0].length];
for (int i = 0; _map.length > i; i++) {
for (int j = 0; _map[0].length > j; j++) {
Image img;
switch (_map[i][j]) {
case "o":
img = getSubimage(_terrain, 0, 0, 3, 6);
break;
case "0":
img = getSubimage(_terrain, 0, 1, 3, 6);
break;
case "a":
img = getSubimage(_terrain, 1, 0, 3, 6);
break;
case "b":
img = getSubimage(_terrain, 2, 0, 3, 6);
break;
case "c":
img = getSubimage(_terrain, 1, 1, 3, 6);
break;
case "d":
img = getSubimage(_terrain, 2, 1, 3, 6);
break;
case "1":
img = getSubimage(_terrain, 0, 2, 3, 6);
break;
case "2":
img = getSubimage(_terrain, 1, 2, 3, 6);
break;
case "3":
img = getSubimage(_terrain, 2, 2, 3, 6);
break;
case "4":
img = getSubimage(_terrain, 0, 3, 3, 6);
break;
case "5":
img = getSubimage(_terrain, 1, 3, 3, 6);
break;
case "6":
img = getSubimage(_terrain, 2, 3, 3, 6);
break;
case "7":
img = getSubimage(_terrain, 0, 4, 3, 6);
break;
case "8":
img = getSubimage(_terrain, 1, 4, 3, 6);
break;
case "9":
img = getSubimage(_terrain, 2, 4, 3, 6);
break;
case "#":
img = getSubimage(_terrain, 0, 5, 3, 6);
break;
case "&":
img = getSubimage(_terrain, 1, 5, 3, 6);
break;
case "%":
img = getSubimage(_terrain, 2, 5, 3, 6);
break;
default:
img = null;
break;
}
Tile tile = new Tile(img);
int x = j;
int y = i;
tile.setLoc(x, y);
_tileMap[i][j] = tile;
}
}
}
public void decOffsetX(final int i) {
_offsetX -= i;
}
public void decOffsetY(final int i) {
_offsetY -= i;
}
public void draw(final Graphics2D _g) {
for (int i = 0; _tileMap.length > i; i++) {
for (int j = 0; _tileMap[0].length > j; j++) {
if (_tileMap[i][j].getImg() != null
&& isVisible(_tileMap[i][j])) {
_g.drawImage(_tileMap[i][j].getImg(), _tileMap[i][j].getX()
- _offsetX, _tileMap[i][j].getY() - _offsetY, null);
} else if (_tileMap[i][j].getImg() == null
&& isVisible(_tileMap[i][j])) {
_g.setColor(_waterColor);
_g.fillRect(_tileMap[i][j].getX() - _offsetX,
_tileMap[i][j].getY() - _offsetY, Gui.TILE_SIZE,
Gui.TILE_SIZE);
}
}
}
}
private Image getSubimage(final BufferedImage img, final int x,
final int y, final int w, final int h) {
return img.getSubimage(x * Gui.TILE_SIZE, y * Gui.TILE_SIZE,
Gui.TILE_SIZE, Gui.TILE_SIZE);
}
public void incOffsetX(final int i) {
_offsetX += i;
}
public void incOffsetY(final int i) {
_offsetY += i;
}
public boolean isVisible(final Tile tile) {
if (tile.getX() - _offsetX < 0 - Gui.TILE_SIZE) {
return false;
} else if (tile.getX() - _offsetX > Gui.WIDTH) {
return false;
}
if (tile.getY() - _offsetY < 0 - Gui.TILE_SIZE) {
return false;
} else if (tile.getY() - _offsetY > Gui.HEIGHT) {
return false;
}
return true;
}
private BufferedImage loadImg(final String s) {
BufferedImage img = null;
try {
img = ImageIO.read(new File("res/" + s));
} catch (IOException e) {
}
return img;
}
private String[][] loadMap(final String s) {
String _s = null;
String string = null;
int appear = 0;
try {
BufferedReader br;
br = new BufferedReader(new FileReader("res/maps/" + s));
while ((_s = br.readLine()) != null) {
string = string + _s + ",";
appear++;
}
br.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
String[] ss = string.replaceAll("null", "").split(",");
String[][] items = new String[appear][];
for (int i = 0; i < items.length; i++) {
items[i] = ss[i].split(" ");
}
return items;
}
public void update() {
if (_offsetX < 0) {
_offsetX += Gui.SPEED;
} else if (_offsetX > _map[0].length * Gui.TILE_SIZE - Gui.WIDTH
- Gui.SPEED) {
_offsetX -= Gui.SPEED;
}
if (_offsetY < 0) {
_offsetY += Gui.SPEED;
} else if (_offsetY > _map.length * Gui.TILE_SIZE - Gui.HEIGHT
- Gui.SPEED) {
_offsetY -= Gui.SPEED;
}
}
}
I'm aware that there probably is a some bugs in the code, and everything is probably not optimal, but this project is VERY early development, and I'm a very inexperienced game developer.. I would appreciate if you would focus on the posted problem, and not my other crap code. :-D
I would really like if you would comment, if you have any suggestions :-D
Thank you
EDIT:
I updated the post, as the problem was unclear. So i uploaded a video, where you can view the bug. I've also cleared some other things :-)
EDIT 2:
Made a new thread on gamedev.stackexchange. It may help you solve the problem: https://gamedev.stackexchange.com/questions/77998/java-game-loop-frame-jumps
SOLUTION:
After a ton of research i came to the conclusion, that it had something to do with the screens refresh rate, and my loops refresh rate.. My screen was simply skipping frames, which was very noticeable!
If you want a indepth solution, google made a video, where they described the problem: https://www.youtube.com/watch?v=hAzhayTnhEI
There are two ways (according to my research) to get rid of this problem:
Either you have to enable VSync through OpenGL, as java doesn't have the option to do so..
Or you have to use a mixture of swing and awt in java (the option i chose).. Here's a thread describing how to do: Smooth Drawing using Java2d without the Opengl or Direct3d Pipelines?
Thank you for all responses!
First of all, an idea is to look into BufferStrategy as a mechanism to create a more robust and performant game loop. Secondly, your game loop is sleeping on varying values dependent on how long your update/rendering takes. This is fine, but your update method should reflect this as well, and not move things around with constant speed. You'll want your entities to move relative to the delta time elapsed since last loop cycle (lastTime - currentTime).
Some of your constants shouldn't be ints. TIME_PR_FRAME for instance, should not really be 16, but 16.67, so make this a double, or at least a float. Your if(sleep < 5) sleep = 5; code makes no sense. If rendering and updating takes very long, you shouldn't waste time sleeping, basically you are deliberately or not introducing jittering in these cases.
Did you debug the code to see what the average values of the sleep variable are, and whether it spikes or not? I'd also do that, if not.

Categories