JavaFX creating a game board with GridPane - java

When the application is run the game board shows fine, although when a tile is clicked the text always shows in the top left corner of the board [0][0].
Also I am struggling with the logic. I have a int board matrix with a function makeMove which to add a players move to the matrix, although I can't get the player to make a move on the matrix and also print a corresponding "O" on the tile index.
How would I create a function that can be passed a row, col index and alter the board matrix and print the player piece on the GUI (e.g.for AI move)
And also when the GUI tile is clicked by the player to then make the corresponding move.
So far I have created a GridPane and added Rectangle with text at each index for the GUI.
I have created a Board class which constructs an int board matrix to hold player moves (P1, P2, empty).
public class Board {
private int[][] board_matrix;
private int board_size;
private int win_length;
public Board(int board_size, int win_length) {
this.board_matrix = new int[board_size][board_size];
this.board_size = board_size;
this.win_length = win_length;
for (int i = 0; i < board_size; i++) {
for (int j = 0; j < board_size; j++) {
this.board_matrix[i][j] = 0;
}
}
}
public void make_move(int player, int x_pos, int y_pos) {
if (player == 1) board_matrix[x_pos][y_pos] = 1;
else board_matrix[x_pos][y_pos] = 2;
}
public class BoardGUI_ extends Application {
private final int BOARD_SIZE = 15;
public Parent createBoard() {
GridPane gameBoard = new GridPane();
gameBoard.setPrefSize(755, 755);
for (int i = 0; i < BOARD_SIZE; i++) {
for (int j = 0; j < BOARD_SIZE; j++) {
Rectangle tile = new Rectangle(50, 50);
tile.setFill(Color.BURLYWOOD);
tile.setStroke(Color.BLACK);
Text text = new Text();
text.setFont(Font.font(40));
GridPane.setRowIndex(tile, i);
GridPane.setColumnIndex(tile, j);
tile.setOnMouseClicked(event -> drawMove(text));
gameBoard.getChildren().addAll(tile, text);
}
}
return gameBoard;
}
public void drawMove(Text text) {
text.setText("O");
text.setFill(Color.BLACK);
}

If you want to add a Text object on top of a Rectangle object wrap both in a StackPane:
public Parent createBoard() {
GridPane gameBoard = new GridPane();
gameBoard.setPrefSize(755, 755);
for (int i = 0; i < BOARD_SIZE; i++) {
for (int j = 0; j < BOARD_SIZE; j++) {
Rectangle tile = new Rectangle(50, 50);
tile.setFill(Color.BURLYWOOD);
tile.setStroke(Color.BLACK);
Text text = new Text();
text.setFont(Font.font(40));
gameBoard.add(new StackPane(tile, text), j, i);
//GridPane.setRowIndex(tile, i);
//GridPane.setColumnIndex(tile, j);
//gameBoard.getChildren().addAll(tile, text);
tile.setOnMouseClicked(event -> drawMove(text));
}
}
return gameBoard;
}

In your loop you are setting the row and column of the 'tile' node, but not the 'text' node. So you have all of your text nodes at 0,0.
It seems you want the tiles to be StackPanes as c0der suggests. With the Text node on top of the tile.
I think makes sense to create a Tile object, possibly derived from StackPane, and use it to manage the each tile. It would be responsible for the background color and text shown. It's not clear at this point if the Rectangle is even needed. You could just set the background and border of the StackPane.

Related

How to draw a grid and paint the grid corners with a point on JavaFX?

I am new to JavaFx and I wanted to know how to draw a grid, where I want to draw points on the grid corners. Should I use a gridpane as a foundation or a linechart ? What are the best classes to use a grid and draw on it ?
I wouldn’t use a GridPane, as its children are not guaranteed to be the same size, only to have their grid cell edges aligned.
A TilePane, however, does guarantee that its cells are the same size. You can then use a Group to combine the TilePane with nodes, such as Circles, which are centered on the points between the grid cells using some basic math:
public class Grid
extends Application {
private int rows = 10;
private int columns = 10;
private int spacing = 8;
#Override
public void start(Stage stage) {
TilePane pane = new TilePane(spacing, spacing);
pane.setPrefColumns(columns);
Group group = new Group(pane);
for (int row = 1; row < rows; row++) {
for (int col = 1; col < columns; col++) {
Circle point = new Circle(2);
point.setFill(Color.BLACK);
// x = ((tilewidth + hgap) * col) - (hgap / 2)
// y = ((tileheight + vgap) * row) - (vgap / 2)
point.centerXProperty().bind(
pane.tileWidthProperty().add(pane.hgapProperty())
.multiply(col)
.subtract(pane.hgapProperty().divide(2)));
point.centerYProperty().bind(
pane.tileHeightProperty().add(pane.vgapProperty())
.multiply(row)
.subtract(pane.vgapProperty().divide(2)));
group.getChildren().add(point);
}
}
// Example grid content
for (int row = 0; row < rows; row++) {
for (int col = 0; col < columns; col++) {
Text text = new Text(String.valueOf(row * rows + col));
pane.getChildren().add(text);
}
}
stage.setScene(new Scene(group));
stage.setTitle("Grid");
stage.show();
}
}

Java-FX Receive Index of Shape-Array when clicked

I am trying to build a battle ship application. So far I managed to display both fields for the player and the enemy. A field consists of 10 x 10 Rectangles - 10 HBox'es in one VBox. This is the method that creates me a field:
private Rectangle[][] field;
public VBox CreateField(){
VBox vbox = new VBox(2);
for(int i = 0; i < this.columns; ++i){
HBox hbox = new HBox(2);
for(int j = 0; j < this.rows; ++j){
this.array[j][i] = 0;
this.field[i][j] = new Rectangle(this.height*j, this.width*i, this.height, this.width);
this.field[i][j].setStroke(Color.BLACK);
this.field[i][j].setFill(Color.LIGHTGRAY);
hbox.getChildren().add(this.field[i][j]);
}
vbox.getChildren().add(hbox);
}
return vbox;
}
This is the result:
I need to receive the Index of a Rectangle when the user clicks one of it.
Can you guys give me an example / code snipped how to accomplish my problem?
I recommend using a GridPane instead of HBoxes and a VBox. You could copy the loop indices to final local variables to access them from a anonymus EventHandler class/lambda expression created inside the loop body or you could use GridPane.getRowIndex/GridPane.getColumnIndex on the GridPane children:
public GridPane CreateField(){
GridPane grid = new GridPane();
grid.setVgap(2);
grid.setHgap(2);
EventHandler<MouseEvent> handler = evt -> {
Node source = (Node) evt.getSource();
int row = GridPane.getRowIndex(source);
int column = GridPane.getColumnIndex(source);
...
};
for(int i = 0; i < this.columns; i++){
for(int j = 0; j < this.rows; j++){
Rectangle rect = new Rectangle(this.height*j, this.width*i, this.height, this.width);
this.array[j][i] = 0;
this.field[i][j] = rect;
rect.setStroke(Color.BLACK);
rect.setFill(Color.LIGHTGRAY);
rect.setOnMouseClicked(handler);
grid.add(rect, j, i);
}
}
return grid;
}
You could also use
final int finalI = i;
final int finalJ = j;
rect.setOnMouseClicked(evt -> {
// TODO: use finalI and finalJ here
});
in the inner loop instead.
Lol, got it faster work than I thought. Here is my solution (How can I get the indexes of each button clicked for my program?)
private EventHandler<? super MouseEvent> createTileHandler(int x, int y) {
return event -> tileHandler(x, y);
}
private void tileHandler (int x, int y){
System.out.println(String.format("Clicked tile at (%d,%d)", x, y));
}
public VBox CreateField(){
VBox vbox = new VBox(2);
for(int i = 0; i < this.columns; ++i){
HBox hbox = new HBox(2);
for(int j = 0; j < this.rows; ++j){
this.array[j][i] = 0;
this.field[i][j] = new Rectangle(this.height*j, this.width*i, this.height, this.width);
this.field[i][j].setStroke(Color.BLACK);
this.field[i][j].setFill(Color.LIGHTGRAY);
this.field[i][j].setOnMouseClicked(createTileHandler(i,j));
hbox.getChildren().add(this.field[i][j]);
}
vbox.getChildren().add(hbox);
}
return vbox;
}
This returns:
Clicked tile at (3,2)
Clicked tile at (3,2)
Clicked tile at (4,4)
Clicked tile at (3,0)
Clicked tile at (8,8)
Clicked tile at (9,9)
Clicked tile at (0,0)
Clicked tile at (0,1)
Clicked tile at (0,2)
When I click one Shape.
Apologizing for this questions ..

How to keep dragged Node in front of others (JavaFX 8)?

I'm trying to implement a chess board with draggable pieces as seen below. However, I'm unable to keep the piece being dragged to stay in front of other nodes to below or right to it. Left and up seems to work fine.
I tried to solve this by declaring the StackPanes forming the checkered background first and all the pieces only after that, as I read Java assigns the z-index based on the order in which the Nodes are added to their Parents. This approach is reflected below. I also tried creating a Group and adding both StackPanes and ImageViews to it in order to be able to use toFront(). Resulted in only the coordinate labels being shown.
How can I achieve the functionality I'm after?
This method creates the board:
public Parent chessBoard() {
GridPane board = new GridPane();
StackPane[][] cells = new StackPane[8][8];
// Create the board first
// (For dragging pieces to work correctly, draggable pieces must be
// added after the whole board, since z-index cannot be set explicitly
// in JavaFX.
for (int row = 0; row < 10; row++) {
for (int col = 0; col < 10; col++) {
// x and y in chess coordinate system (0-indexed)
int[] invertedY = {-1,7,6,5,4,3,2,1,0,-1};
int x = col - 1;
int y = invertedY[row];
// Coordinate labels
String[] abcLabels = {"A","B","C","D","E","F","G","H"};
if (row == 9 || row == 0) {
if (col == 0 || col == 9) continue;
Label label = new Label(abcLabels[x]);
label.setTextAlignment(TextAlignment.CENTER);
board.add(label, col, row);
continue;
} else if (col == 0 || col == 9) {
Label label = new Label(Integer.toString(y + 1));
board.add(label, col, row);
continue;
}
// Cell background color
Square square = game.getBoard().getSquare(x, y);
Color color = square.getColor() == ChessColor.BLACK
? Color.PERU : Color.BLANCHEDALMOND;
StackPane cell = cells[y][x] = new StackPane();
cell.setMaxSize(60, 60);
cell.setMinSize(60, 60);
cell.setBackground(new Background(
new BackgroundFill(color, null, null)));
board.add(cell, col, row);
}
}
// Finally, add pieces to their respective cells
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
Square square = game.getBoard().getSquare(x, y);
Piece occupant = square.getOccupant();
if (occupant != null) {
String path = "/resources/" + occupant + ".png";
Image image =
new Image(getClass().getResourceAsStream(path));
DraggablePieceIcon imageView =
new DraggablePieceIcon(image);
imageView.setManaged(false);
cells[y][x].getChildren().add(imageView);
}
}
}
return board;
}
This class makes the draggable icons:
public class DraggablePieceIcon extends ImageView {
private double mouseX;
private double mouseY;
public DraggablePieceIcon(Image image) {
super(image);
setOnMousePressed(event -> {
mouseX = event.getSceneX();
mouseY = event.getSceneY();
});
setOnMouseDragged(event -> {
double deltaX = event.getSceneX() - mouseX;
double deltaY = event.getSceneY() - mouseY;
relocate(getLayoutX() + deltaX, getLayoutY() + deltaY);
mouseX = event.getSceneX();
mouseY = event.getSceneY();
});
}
}
And here's what I'm seeing:
You're adding the cells row by row from left to right. Since you add the pieces to the cells, the descendants of a cell cover the contents of cells in rows above or in the same row and left to the cell and will be covered by contents of all other cells.
To fix this you could make the parent of a dragged item the topmost node in the GridPane:
public DraggablePieceIcon(Image image) {
super(image);
setOnMousePressed(event -> {
mouseX = event.getSceneX();
mouseY = event.getSceneY();
// make cell containing this piece the top-most cell
this.getParent().toFront()
});
...
}
Note that this solution will require you to implement some logic that makes the pieces children of the cells they are moved to and move the pieces to the center of those cells. Otherwise a piece could be covered by other cells, if you drag the piece inside such a cell later...
An alternative would be to make the pieces children of the GridPane itself. You allow the pieces to be dragged around independent of the cells anyways; the association between cell and piece is important for the model (i.e. in this case the implementation of the chess rules) not for the view and usually these parts are kept seperate.

Java Game of life wrap around

i have an up and running version of the game of life, but one thing i can not figure out is how to wrap around the Grid or Board, i am guessing is has to be something to do with the neighbor counts and the grid, i need a way to indicate that the array wraps.
The rules :
The universe of the Game of Life is an infinite two-dimensional
orthogonal grid of square cells,
each of which is in one of two possible states, live or dead.
Every cell interacts with its eight neighbors, which are the cells
that are directly horizontally,
vertically, or diagonally adjacent. At each step in time, the
following transitions occur:
1.Any live cell with fewer than two live neighbours dies, as if caused by underpopulation.
2.Any live cell with more than three live neighbours dies, as if by overcrowding.
3.Any live cell with two or three live neighbours lives on to the next generation.
4.Any dead cell with exactly three live neighbours becomes a live cell.
The initial pattern constitutes the seed of the system. The first
generation is created by applying the above rules simultaneously to
every cell in the seed—births and deaths happen simultaneously.
Here's some of the code that will be relating to the grid/board; Called
CellsGrid Cells;
GameOfLife2(int nbRow, int nbCol) {
super(" New GameOfLife");
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// create the labels (2 more on each size) these wont be shown
// but will be used in calculating the cells alive around
Cells = new CellsGrid[nbRow+2][nbCol+2];
for(int r = 0; r < nbRow+2; r++) {
for(int c = 0; c < nbCol+2; c++) {
Cells[r][c] = new CellsGrid();
}
}
for(int r = 1; r < nbRow+1; r++) {
for(int c = 1; c < nbCol+1; c++) {
panel.add(Cells[r][c]);
Cells[r][c].addNeighbour(Cells[r-1][c]); // North
Cells[r][c].addNeighbour(Cells[r+1][c]); // South
Cells[r][c].addNeighbour(Cells[r][c-1]); // West
Cells[r][c].addNeighbour(Cells[r][c+1]); // East
Cells[r][c].addNeighbour(Cells[r-1][c-1]); // North West
Cells[r][c].addNeighbour(Cells[r-1][c+1]); // North East
Cells[r][c].addNeighbour(Cells[r+1][c-1]); // South West
Cells[r][c].addNeighbour(Cells[r+1][c+1]); // South East
}
}
if(!gameRunning)
return;
++generation;
CellsIteration.setText("Generation: " + generation);
for(int r = 0; r < Cells.length; r++) {
for(int c = 0; c < Cells[r].length; c++) {
Cells[r][c].checkState();
}
}
for(int r = 0; r < Cells.length; r++) {
for(int c = 0; c < Cells[r].length; c++) {
Cells[r][c].updateState();
}
}
}
void checkState() {
// number alive around
int NumNeighbours = 0; // number alive neighbours
// see the state of my neighbour
for(int i = 0; i < numNeighbours; i++)
NumNeighbours += neighbour[i].state;
// newState
if(state == 1) { // if alive
if(NumNeighbours < 2) // 1.Any live cell with fewer than two live neighbours dies
newState = 0;
if(NumNeighbours > 3) // 2.Any live cell with more than three live neighbours dies
newState = 0;
}
else {
if(NumNeighbours == 3) // 4.Any dead cell with exactly three live neighbours becomes a live cell
newState = 1;
}
}
full code:
package com.ggl.life;
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import javax.swing.*;
public class GameOfLife2 extends JFrame implements ActionListener {
/**
*
*/
public static Random random = new Random();
private static final long serialVersionUID = 1L;
static final Color[] color = {Color.YELLOW, Color.BLACK};
// size in pixel of every label
static final int size = 15;
static final Dimension dim = new Dimension(size, size);
static final int GenDelay = 200;
// the cells labels
private CellsGrid[][] Cells;
// timer that fires the next generation
private Timer timer;
// generation counter
private int generation = 0;
private JLabel CellsIteration = new JLabel("Generation: 0");
// the 3 buttons
private JButton clearBtn = new JButton("Clear"),
PauseBtn = new JButton("Pause"),
StartBtn = new JButton("Start");
// the slider for the speed
// state of the game (running or pause)
private boolean gameRunning = false;
// if the mouse is down or not
private boolean mouseDown = false;
GameOfLife2(int nbRow, int nbCol) {
super(" New GameOfLife");
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// create the labels (2 more on each size) these wont be shown
// but will be used in calculating the cells alive around
Cells = new CellsGrid[nbRow+2][nbCol+2];
for(int r = 0; r < nbRow+2; r++) {
for(int c = 0; c < nbCol+2; c++) {
Cells[r][c] = new CellsGrid();
}
}
// panel in the center with the labels
JPanel panel = new JPanel(new GridLayout(nbRow, nbCol, 1, 1));
panel.setBackground(Color.BLACK);
panel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
// add each label (not the one on the border) to the panel and add to each of them its neighbours
for(int r = 1; r < nbRow+1; r++) {
for(int c = 1; c < nbCol+1; c++) {
panel.add(Cells[r][c]);
Cells[r][c].addNeighbour(Cells[r-1][c]);
//Cells[r][c].addNeighbour(getCellSafe(r-1, c)); // North
Cells[r][c].addNeighbour(Cells[r+1][c]); // South
//Cells[r][c].addNeighbour(getCellSafe(r+1, c));
Cells[r][c].addNeighbour(Cells[r][c-1]); // West
//Cells[r][c].addNeighbour(getCellSafe(r, c-1));
Cells[r][c].addNeighbour(Cells[r][c+1]); // East
//Cells[r][c].addNeighbour(getCellSafe(r, c+1));
Cells[r][c].addNeighbour(Cells[r-1][c-1]); // North West
//Cells[r][c].addNeighbour(getCellSafe(r-1, c-1));
Cells[r][c].addNeighbour(Cells[r-1][c+1]); // North East
//Cells[r][c].addNeighbour(getCellSafe(r-1, c+1));
Cells[r][c].addNeighbour(Cells[r+1][c-1]); // South West
//Cells[r][c].addNeighbour(getCellSafe(r+1, c-1));
Cells[r][c].addNeighbour(Cells[r+1][c+1]); // South East
//Cells[r][c].addNeighbour(getCellSafe(r+1, +c));
}
}
// now the panel can be added
add(panel, BorderLayout.CENTER);
// the bottom panel with the buttons the generation label and the slider
// this panel is formed grid panels
panel = new JPanel(new GridLayout(1,3));
// another panel for the 3 buttons
JPanel buttonPanel = new JPanel(new GridLayout(1,3));
clearBtn.addActionListener(this);
buttonPanel.add(clearBtn);
PauseBtn.addActionListener(this);
PauseBtn.setEnabled(false); // game is pause the pause button is disabled
buttonPanel.add(PauseBtn);
StartBtn.addActionListener(this);
buttonPanel.add(StartBtn);
// add the 3 buttons to the panel
panel.add(buttonPanel);
// the generation label
CellsIteration.setHorizontalAlignment(SwingConstants.CENTER);
panel.add(CellsIteration);
// in the JFrame
add(panel, BorderLayout.NORTH);
// put the frame on
setLocation(20, 20);
pack(); // adjust to the window size
setVisible(true);
// start the thread that run the cycles of life
timer = new Timer(GenDelay , this);
}
private CellsGrid getCellSafe(int r0, int c0) {
int r = r0 % Cells.length; // Cells.length is effectively nbRow
if (r < 0) r += Cells.length; // deal with how % works for negatives
int c = c0 % Cells[0].length; // Cells[0].length is effectively nbCol
if (c < 0) c += Cells[0].length; // deal with how % works for negatives
return Cells[r][c];
}
//end of game of life
// called by the Timer and the JButtons
public synchronized void actionPerformed(ActionEvent e) {
// test the JButtons first
Object o = e.getSource();
// the clear button
if(o == clearBtn) {
timer.stop(); // stop timer
gameRunning = false; // flag gamme not running
PauseBtn.setEnabled(false); // disable pause button
StartBtn.setEnabled(true); // enable go button
// clear all cells
for(int r = 1; r < Cells.length ; r++) {
for(int c = 1; c < Cells[r].length ; c++) {
Cells[r][c].clear();
}
}
// reset generation number and its label
generation = 0;
CellsIteration.setText("Generation: 0");
return;
}
// the pause button
if(o == PauseBtn) {
timer.stop(); // stop timer
gameRunning = false; // flag not running
PauseBtn.setEnabled(false); // disable myself
StartBtn.setEnabled(true); // enable go button
return;
}
// the go button
if(o == StartBtn) {
PauseBtn.setEnabled(true); // enable pause button
StartBtn.setEnabled(false); // disable myself
gameRunning = true; // flag game is running
timer.setDelay(GenDelay);
timer.start();
return;
}
// not a JButton so it is the timer
// set the delay for the next time
timer.setDelay(GenDelay);
// if the game is not running wait for next time
if(!gameRunning)
return;
++generation;
CellsIteration.setText("Generation: " + generation);
for(int r = 0; r < Cells.length; r++) {
for(int c = 0; c < Cells[r].length; c++) {
Cells[r][c].checkState();
}
}
for(int r = 0; r < Cells.length; r++) {
for(int c = 0; c < Cells[r].length; c++) {
Cells[r][c].updateState();
}
}
}
//end of action
// to start the whole thing as a Java application
public static void main(String[] arg) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new GameOfLife2(50, 50);
}
});
}
// A class that extends JLabel but also check for the neigbour
// when asked to do so
class CellsGrid extends JLabel implements MouseListener {
/**
*
*/
private static final long serialVersionUID = 1L;
private int state, newState;
private int numNeighbours;
private CellsGrid[] neighbour = new CellsGrid[8]; // array of total neighbours with possibility of 8
CellsGrid() {
state = newState = 0; // Dead
setOpaque(true); // so color will be showed
setBackground(color[0]); //set colour of dead cell
addMouseListener(this); // to select new LIVE cells
this.setPreferredSize(dim); //set size a new cells
}
// to add a neighbour
void addNeighbour(CellsGrid n) {
neighbour[numNeighbours++] = n;
}
// to see if I should live or not
void checkState() {
// number alive around
int NumNeighbours = 0; // number alive neighbours
// see the state of my neighbour
for(int i = 0; i < numNeighbours; i++)
NumNeighbours += neighbour[i].state;
// newState
if(state == 1) { // if alive
if(NumNeighbours < 2) // 1.Any live cell with fewer than two live neighbours dies
newState = 0;
if(NumNeighbours > 3) // 2.Any live cell with more than three live neighbours dies
newState = 0;
}
else {
if(NumNeighbours == 3) // 4.Any dead cell with exactly three live neighbours becomes a live cell
newState = 1;
}
}
// after the run switch the state to new state
void updateState() {
if(state != newState) { // do the test to avoid re-setting same color for nothing
state = newState;
setBackground(color[state]);
}
}
// called when the game is reset/clear
void clear() {
if(state == 1 || newState == 1) {
state = newState = 0;
setBackground(color[state]);
}
}
#Override
public void mouseClicked(MouseEvent arg0) {
}
// if the mouse enter a cell and it is down we make the cell alive
public void mouseEntered(MouseEvent arg0) {
if(mouseDown) {
state = newState = 1;
setBackground(color[1]);
}
}
#Override
public void mouseExited(MouseEvent arg0) {
}
// if the mouse is pressed on a cell you register the fact that it is down
// and make that cell alive
public void mousePressed(MouseEvent arg0) {
mouseDown = true;
state = newState = 1;
setBackground(color[1]);
}
// turn off the fact that the cell is down
public void mouseReleased(MouseEvent arg0) {
mouseDown = false;
}
}
}
As UnholySheep you need to learn % operator. % is the way to calculate reminder of division aka modulo. See https://en.wikipedia.org/wiki/Modulo_operation
The simplest way to apply it here is something like this. First introduce method getCellSafe
private CellsGrid getCellSafe(int r0, int c0) {
int r = r0 % Cells.length; // Cells.length is effectively nbRow
if (r < 0) r += Cells.length; // deal with how % works for negatives
int c = c0 % Cells[0].length; // Cells[0].length is effectively nbCol
if (c < 0) c += Cells[0].length; // deal with how % works for negatives
return Cells[r][c];
}
and then use this method instead direct access such as
Cells[r][c].addNeighbour(getCellSafe(r-1, c); // North
and also get rid of your + 2 in your nbCol and nbRow
Update: Fix for L-shape
The bug for "L-shape" is in your copy of line for "South East" where you converted c+1 into +c
Cells[r][c].addNeighbour(Cells[r+1][c+1]); // South East
//Cells[r][c].addNeighbour(getCellSafe(r+1, +c));
Bigger improvements
Generally your code is pretty bad. There are a lot of logic duplications (see DRY principle) and other bad code such as huge ActionListener. Here is my attempt to clean it up a bit:
public class GameOfLife2 extends JFrame
{
/**
*
*/
public static Random random = new Random();
static final Color[] color = {Color.YELLOW, Color.BLACK};
// size in pixel of every label
static final int cellSize = 15;
static final Dimension cellDim = new Dimension(cellSize, cellSize);
static final int GenDelay = 200;
// the cells labels
private CellsGrid[][] Cells;
// that fires the next generation
private Timer timer;
// generation counter
private int generation = 0;
private JLabel CellsIteration = new JLabel("Generation: 0");
// the 3 buttons
private JButton clearBtn = new JButton("Clear");
private JButton PauseBtn = new JButton("Pause");
private JButton StartBtn = new JButton("Start");
private JButton StepBtn = new JButton("Step");
// the slider for the speed
// state of the game (running or pause)
private boolean gameRunning = false;
GameOfLife2(int nbRow, int nbCol)
{
super("New GameOfLife");
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
Cells = new CellsGrid[nbRow][nbCol];
for (int r = 0; r < nbRow; r++)
{
for (int c = 0; c < nbCol; c++)
{
Cells[r][c] = new CellsGrid();
}
}
// panel in the center with the labels
JPanel panel = new JPanel(new GridLayout(nbRow, nbCol, 1, 1));
panel.setBackground(Color.BLACK);
panel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
// add each label (not the one on the border) to the panel and add to each of them its neighbours
for (int r = 0; r < nbRow; r++)
{
for (int c = 0; c < nbCol; c++)
{
CellsGrid curCell = Cells[r][c];
panel.add(curCell);
for (int dr = -1; dr <= 1; dr++)
{
for (int dc = -1; dc <= 1; dc++)
{
CellsGrid neighbor = getCellSafe(r + dr, c + dc);
if (neighbor != curCell)
curCell.addNeighbour(neighbor);
}
}
}
}
// now the panel can be added
add(panel, BorderLayout.CENTER);
// the bottom panel with the buttons the generation label and the slider
Box headerPanel = Box.createHorizontalBox();
Box buttonPanel = Box.createHorizontalBox();
// old pre-Java 8 syntax
//clearBtn.addActionListener(new ActionListener()
//{
// #Override
// public void actionPerformed(ActionEvent e)
// {
// clearGame();
// }
//});
clearBtn.addActionListener((e) -> clearGame()); // holy Java 8 lambda syntax
buttonPanel.add(clearBtn);
PauseBtn.addActionListener((e) -> stopGame());
buttonPanel.add(PauseBtn);
StartBtn.addActionListener((e) -> startGame());
buttonPanel.add(StartBtn);
StepBtn.addActionListener((e) -> stepToNextGeneration());
buttonPanel.add(StepBtn);
// the generation label
CellsIteration.setHorizontalAlignment(SwingConstants.CENTER);
headerPanel.add(Box.createHorizontalStrut(10));
headerPanel.add(buttonPanel);
headerPanel.add(Box.createHorizontalStrut(10));
headerPanel.add(Box.createHorizontalGlue());
headerPanel.add(Box.createHorizontalStrut(10));
headerPanel.add(CellsIteration);
headerPanel.add(Box.createHorizontalGlue()); // if you want "generation" label closer to center
headerPanel.add(Box.createHorizontalStrut(10));
// in the JFrame
add(headerPanel, BorderLayout.NORTH);
// put the frame on
setLocation(20, 20);
gameRunning = false;
generation = 0;
updateGenerationTitle();
updateButtonsState();
pack(); // adjust to the window size
setMinimumSize(new Dimension(600, 500));
setVisible(true);
// start the thread that run the cycles of life
timer = new Timer(GenDelay, (e) -> onTimerStep());
}
private CellsGrid getCellSafe(int r0, int c0)
{
int r = r0 % Cells.length; // Cells.length is effectively nbRow
if (r < 0) r += Cells.length; // deal with how % works for negatives
int c = c0 % Cells[0].length; // Cells[0].length is effectively nbCol
if (c < 0) c += Cells[0].length; // deal with how % works for negatives
return Cells[r][c];
}
private void updateButtonsState()
{
PauseBtn.setEnabled(gameRunning);
StartBtn.setEnabled(!gameRunning);
StepBtn.setEnabled(!gameRunning);
}
private void updateGenerationTitle()
{
CellsIteration.setText("Generation: " + generation);
}
private void startGame()
{
gameRunning = true; // flag game is running
updateButtonsState();
timer.setDelay(GenDelay);
timer.start();
}
private void stopGame()
{
timer.stop(); // stop timer
gameRunning = false; // flag not running
updateButtonsState();
}
private void clearGame()
{
stopGame();
// clear all cells
for (int r = 0; r < Cells.length; r++)
{
for (int c = 0; c < Cells[r].length; c++)
{
Cells[r][c].clear();
}
}
// reset generation number and its label
generation = 0;
updateGenerationTitle();
}
private void stepToNextGeneration()
{
++generation;
updateGenerationTitle();
for (int r = 0; r < Cells.length; r++)
{
for (int c = 0; c < Cells[r].length; c++)
{
Cells[r][c].checkState();
}
}
for (int r = 0; r < Cells.length; r++)
{
for (int c = 0; c < Cells[r].length; c++)
{
Cells[r][c].updateState();
}
}
}
private void onTimerStep()
{
if (gameRunning)
stepToNextGeneration();
}
// to start the whole thing as a Java application
public static void main(String[] arg)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
new GameOfLife2(50, 50);
}
});
}
// A class that extends JLabel but also check for the neigbour
// when asked to do so
static class CellsGrid extends JLabel implements MouseListener
{
private boolean alive = false;
private boolean newAlive = false;
private final ArrayList<CellsGrid> neighbours = new ArrayList<>(8); // array of total neighbours with possibility of 8
CellsGrid()
{
setOpaque(true); // so color will be showed
addMouseListener(this); // to select new LIVE cells
setPreferredSize(cellDim); //set size a new cells
updateColor();
}
// to add a neighbour
void addNeighbour(CellsGrid n)
{
neighbours.add(n);
}
// to see if I should live or not
void checkState()
{
// number alive around
int NumNeighbours = 0; // number alive neighbours
// see the state of my neighbour
for (CellsGrid neighbour : neighbours)
NumNeighbours += neighbour.alive ? 1 : 0;
// 1. Any live cell with fewer than two live neighbours dies
// 2. Any live cell with two or three live neighbours lives on to the next generation.
// 3. Any live cell with more than three live neighbours dies
// 4. Any dead cell with exactly three live neighbours becomes a live cell
if (alive)
{
newAlive = (NumNeighbours == 2) || (NumNeighbours == 3);
}
else
{
newAlive = (NumNeighbours == 3);
}
}
// after the run switch the state to new state
void updateState()
{
alive = newAlive;
updateColor();
}
// called when the game is reset/clear
void clear()
{
alive = newAlive = false;
updateColor();
}
private void updateColor()
{
setBackground(color[alive ? 1 : 0]);
}
#Override
public void mouseClicked(MouseEvent arg0)
{
}
// if the mouse enter a cell and it is down we make the cell alive
#Override
public void mouseEntered(MouseEvent e)
{
boolean mouseDown = (e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0;
if (mouseDown)
{
alive = newAlive = true;
updateColor();
}
}
#Override
public void mouseExited(MouseEvent e)
{
}
#Override
public void mousePressed(MouseEvent arg0)
{
// state = newState = true;
alive = newAlive = !newAlive; //invert state is more useful, so you can clear something
updateColor();
}
#Override
public void mouseReleased(MouseEvent arg0)
{
}
}
}
This code seems to work including Glider flying diagonally with wrapping around the field.
Some notes:
I split your single ActionListener into independent ones for each thing and used Java 8 syntax to make them short and nice at registration
I introduced a few updateXyz and stepToNextGeneration methods to drastically reduce code duplication
MouseEvent already provides information whether button is down or not.
I added "Step" button that was needed for me to debug your code.
There are other layouts besides GridLayout
There is not much sense in defining serialVersionUID in UI (JComponent) classes as they are effectively not serializable (although they do implement Serializable interface). See also Swing components and serialization
Update with more answers for comments
If I wanted to have some pre-made initial state, I'd made a simple class (more like structute) Point with just two fields row and column and had a List or array of them, then did clear and iterated over the collection to make alive those cells that are in the list. If I wanted to go even further, I'd probably added one more wrapping class (structure) SavedGame or something to save width and height and a collection of Points; and made whole UI dynamic instead of building it once in constructor; and then added ability to save/read such configuration from some file
As for random, there is a java.util.Random class with useful int nextInt(int n) method. So I'd ask the user how many random cells should be made alive and then run a loop till that number and using Random generated new alive cells by generating row and column. Note that if you want to fill significant portion of cells, you might need to check if the next randomly cell you selected is already alive and if yes, "roll the dice" one more time. If you want to fill even more, you'll need to change algorithm to more complicated or filling 90% of cells might take forever. One idea might be to generated single cell index in range (0; row * height - count of already alive cells) rather than independent row and column and then just go through cells starting from top-left to bottom-right counting only dead cells and making alive corresponding cell. In such way you make sure that each new alive cell takes the same amount of time to generate.

GridLayout stacks all JPanels on first cell

I am trying to create a map for a game I am making with a JPanel that uses gridLayout. In my first tests I use a 5x5 grid and create my small panels which are a subclass of JPanel. My program creates them fine but when I add all of the panels into the larger panel and display it, only the first square shows up and the rest is blank. Why does it do that?
Here is my code for the MapSpace(smaller panel):
import javax.swing.*;
import java.awt.*;
public class MapSpace extends JPanel{
private int ownerTag;
private int xPos, yPos;
public MapSpace(){
xPos = 0;
yPos = 0;
ownerTag = 0;
setBackground(Color.WHITE);
}
public MapSpace(MapSpace m){
xPos = m.getX();
yPos = m.getY();
ownerTag = m.getID();
setBackground(m.getColor());
}
and here is my code for the Map:
import javax.swing.*;
import java.awt.*;
import java.util.*;
public class Map extends JPanel{
private int cols, rows;
private int randCol, randRow;
private MapSpace[][] spaces;
Random gen = new Random();
public Map(int w, int h){
cols = h;
rows = w;
setLayout(new GridLayout(cols, rows));
setBackground(Color.WHITE);
spaces = new MapSpace[cols][rows];
for(int i = 0; i < cols; i++){
for(int j = 0; j < rows; j++){
MapSpace panel = new MapSpace(i, j);
spaces[i][j] = panel;
}
}
assignSpaces(3);
setColors();
for(int i = 0; i < cols; i++){
for(int j = 0; j < rows; j++){
MapSpace spot = new MapSpace(spaces[i][j]);
add(spot);
}
}
setSize(400, 400);
}
the second nested for loop is where all the mapSpaces are added but when I put the map in a JFrame and display it in a GUI window only one small square in the top left corner appears.
Why are you trying to create a MapSpace with an instance of MapSpace?
Just create the MapSpace with the parameters you want and then add the MapSpace to your Array and the panel at the same time.
and display it in a GUI window only one small square in the top left corner appears.
Probably because you don't give a preferredSize() size to your MapSpace class so it defaults to (10, 10) which is the size for a panel using a FlowLayout with no components added to it. So since you create a 5x5 grid you probably see a (50, 50) white square.
Override the getPreferredSize() of your MapSpace class to return the default Dimension for each square.

Categories