Currently, I'm working on an AI for a simple turn-based game. The way I have the game set up is as following (in pseudo-code):
players = [User, AI];
(for player : players){
player.addEventlistener(MoveListener (moveData)->move(moveData));
}
players[game.getTurn()].startTurn();
the move function:
move(data){
game.doStuff(data);
if(game.isOver())
return;
game.nextTurn();
players[game.getTurn()].startTurn();
}
This results in the following recursion:
start turn
player/AI makes a move
move function gets called
the next player starts their turn
...
This repeats until the game is over - note that the game is of finite length and doesn't go past ~50 moves. Now, even though the recursion is finite, I get a stackoverflow error. My question is: is there any way to fix this? Is there something wrong with the recursion after all? Or should I implement a game loop instead? I understand how this would work if AIs were to play against each other, but how would this work if the program had to wait for user input?
EDIT
Here are the relevant classes to the recursion:
Connect4 class:
package connect4;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class Connect4 extends Application {
Group root = new Group();
GameSquare[][] squares;
GameButton[] buttons;
int currentTurn;
int columns = 7;
int rows = 6;
Text gameState;
Player[] players;
Game game;
#Override
public void start(Stage primaryStage) {
int size = 50;
int padding = 10;
gameState = new Text();
gameState.setX(padding);
gameState.setY((rows+1)*size+(rows+3)*padding);
root.getChildren().add(gameState);
buttons = new GameButton[columns];
for(int i = 0; i < buttons.length; i++){
buttons[i] = new GameButton(i);
buttons[i].setMaxWidth(size);
buttons[i].setMaxHeight(size);
buttons[i].setLayoutX(i*size+(i+1)*padding);
buttons[i].setLayoutY(padding);
buttons[i].setMouseTransparent(true);
buttons[i].setVisible(false);
root.getChildren().add(buttons[i]);
}
players = new Player[2];
players[0] = new UserControlled(buttons);
players[1] = new AI();
MoveListener listener = (int i) -> {move(i);};
for(Player player : players)
player.addListener(listener);
game = new Game(columns, rows, players.length);
squares = new GameSquare[columns][rows];
for(int x = 0; x < columns; x++){
for(int y = 0; y < rows; y++){
squares[x][y] = new GameSquare(
x*size+(x+1)*padding,
(y+1)*size+(y+2)*padding,
size,
size,
size,
size
);
root.getChildren().add(squares[x][y]);
}
}
players[game.getTurn()].startTurn(game);
updateTurn();
updateSquares();
draw(primaryStage);
}
public void move(int i){
game.move(i);
updateSquares();
if(game.isGameOver()){
if(game.isTie()){
tie();
return;
} else {
win();
return;
}
}
updateTurn();
players[game.getTurn()].startTurn(game);
}
private void updateSquares(){
int[][] board = game.getBoard();
for(int x = 0; x < columns; x++){
for(int y = 0; y < rows; y++){
squares[x][y].setOwner(board[x][y]);
}
}
}
private void updateTurn(){
gameState.setText("Player " + game.getTurn() + "'s turn");
}
public static void main(String[] args) {
launch(args);
}
private void draw(Stage primaryStage){
Scene scene = new Scene(root, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
private void win(){
gameState.setText("Player " + game.getWinner() + " has won the game!");
}
private void tie(){
gameState.setText("It's a tie!");
}
}
Game class:
package connect4;
public class Game {
private int turn = 0;
private int[][] board;
private int columns;
private int rows;
private int players;
private boolean gameOver = false;
private boolean tie = false;
private int winner = -1;
public Game(int columns, int rows, int playerCount){
this.columns = columns;
this.rows = rows;
board = new int[columns][rows];
for(int x = 0; x < columns; x++){
for(int y = 0; y < rows; y++){
board[x][y] = -1;
}
}
players = playerCount;
}
public int[][] getBoard(){
return board;
}
public int getTurn(){
return turn;
}
private void updateTurn(){
turn++;
if(turn >= players)
turn = 0;
}
public boolean isGameOver(){
return gameOver;
}
private void win(int player){
gameOver = true;
winner = player;
}
public int getWinner(){
return winner;
}
private void tie(){
gameOver = true;
tie = true;
}
public boolean isTie(){
return tie;
}
public void move(int i){
if(gameOver)
return;
if(columnSpaceLeft(i) == 0){
return;
}
board[i][columnSpaceLeft(i)-1] = turn;
checkWin(turn);
checkFullBoard();
if(gameOver)
return;
updateTurn();
}
private void checkFullBoard(){
for(int i = 0; i < columns; i++){
if(columnSpaceLeft(i) != 0)
return;
}
tie();
}
public int columnSpaceLeft(int column){
for(int i = 0; i < board[column].length; i++){
if(board[column][i] != -1)
return i;
}
return board[column].length;
}
public int[] getAvailableColumns(){
int columnCount = 0;
for(int i = 0; i < board.length; i++){
if(columnSpaceLeft(i) != 0)
columnCount++;
}
int[] columns = new int[columnCount];
int i = 0;
for(int j = 0; j < board.length; j++){
if(columnSpaceLeft(i) != 0){
columns[i] = j;
i++;
}
}
return columns;
}
private Boolean checkWin(int player){
//vertical
for(int x = 0; x < columns; x++){
int count = 0;
for(int y = 0; y < rows; y++){
if(board[x][y] == player)
count++;
else
count = 0;
if(count >= 4){
win(player);
return true;
}
}
}
//horizontal
for(int y = 0; y < rows; y++){
int count = 0;
for(int x = 0; x < columns; x++){
if(board[x][y] == player)
count++;
else
count = 0;
if(count >= 4){
win(player);
return true;
}
}
}
//diagonal
for(int x = 0; x < columns; x++){
for(int y = 0; y < rows; y++){
int count = 0;
//diagonaal /
if(!(x > columns-4 || y < 3) && board[x][y] == player){
count ++;
for(int i = 1; i <= 3; i++){
if(board[x+i][y-i] == player){
count++;
if(count >= 4){
win(player);
return true;
}
} else {
count = 0;
break;
}
}
}
//diagonal \
if(!(x > columns-4 || y > rows-4) && board[x][y] == player){
count ++;
for(int i = 1; i <= 3; i++){
if(board[x+i][y+i] == player){
count++;
if(count >= 4){
win(player);
return true;
}
} else {
count = 0;
break;
}
}
}
}
}
return false;
}
}
UserControlled class:
package connect4;
import java.util.ArrayList;
import java.util.List;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
public class UserControlled implements Player {
private List<MoveListener> listeners = new ArrayList<MoveListener>();
private GameButton[] buttons;
private boolean active = false;
public UserControlled(GameButton[] buttons){
this.buttons = buttons;
}
#Override
public void addListener(MoveListener listener){
listeners.add(listener);
}
#Override
public void startTurn(Game game){
System.out.println(0);
active = true;
for(int i = 0; i < buttons.length; i++){
if(game.columnSpaceLeft(i) != 0){
setButton(i, true);
buttons[i].setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
move(( (GameButton) e.getTarget()).getColumn());
}
});
}
}
}
private void move(int i){
if(!active)
return;
active = false;
disableButtons();
for(MoveListener listener : listeners)
listener.onMove(i);
}
private void disableButtons(){
for(int i = 0; i < buttons.length; i++){
setButton(i, false);
}
}
private void setButton(int i, boolean enable){
if(enable){
buttons[i].setMouseTransparent(false);
buttons[i].setVisible(true);
} else {
buttons[i].setMouseTransparent(true);
buttons[i].setVisible(false);
}
}
}
The AI class is basically the same as a stripped down UserControlled class, except the startTurn method:
int[] columns = game.getAvailableColumns();
move(columns[rng.nextInt(columns.length)]);
The MoveListener interface is very simple:
public interface MoveListener {
void onMove(int i);
}
The stack trace:
Exception in thread "JavaFX Application Thread" java.lang.StackOverflowError
at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:142)
at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:49)
at javafx.scene.text.Text.setText(Text.java:370)
//note that the three lines above this are different every time
//as the application crashes at a different point
at connect4.Connect4.updateTurn(Connect4.java:107)
at connect4.Connect4.move(Connect4.java:93)
at connect4.Connect4.lambda$start$0(Connect4.java:49)
at connect4.AI.move(AI.java:13)
at connect4.AI.startTurn(AI.java:24)
at connect4.Connect4.move(Connect4.java:94)
at connect4.Connect4.lambda$start$0(Connect4.java:49)
at connect4.AI.move(AI.java:13)
...etc
In general, you shouldn't use a recursion except you are pretty sure about what you are doing.
Think this, every time you call the next step, you are saving all the context, with all the local variables in the stack. In a game, it could be a lot of stuff.
A common game loop in a turn based game would be something like:
while(!gameFinished()){
for(player in players){
player.doTurn();
}
}
Take into account too, that recursion is slow, because it has to save all the context and it takes time, so, in general, think three times before trying to use a recursion.
EDIT
To process the input you could use something like this:
CompletableFuture.supplyAsync(this::waitUserInput)
.thenAccept(this::processUserInput)
Here you can find how it works:
http://www.deadcoderising.com/java8-writing-asynchronous-code-with-completablefuture/
With this, your code keeps running, so keep in mind that in the next line of code you won't have the input. When it gets the input, it will call the proccessUserInput method.
Another way to do this, is to check every frame if any key was pressed, and that's okay too.
Here you can find a way to do that:
How do I check if the user is pressing a key?
The way you should do things depends on the size of your project. If you will be checking for key presses all the time, maybe it's a good idea to build some sort of event system for that.
In the other hand, I recommend you to use a game engine like Unreal or Unity. If you want to keep with Java, there are a lot of libraries for games that handle a lot of common problems like this.
For example:
https://www.lwjgl.org/
You can find a lot of tutorials of turn-based games made with that library.
Good luck!
Related
I'm trying to make an Othello AI, currently trying to implement MiniMax. I've created a board object, from the current board I want to create a list of all "child" boards, these are copies of the current board but with each having one legal move applied to them. I'm having an issue creating multiple "child" boards, so far my code is applying all of the legal moves to just one board and only having that single board in the children board list. Any help would be much appreciated.
minimax method in the MiniMax class
public static void minimax(){
ArrayList<int[]> legalMoves;
legalMoves = gameHandler.potentialMovesList();
int min = -1000;
int max = 1000;
//-----legal moves list-----
char disc;
if (Main.blackTurn = true){
disc = 'b';
}
else {
disc = 'w';
}
int count = 0;
for (int[] i : legalMoves) {
System.out.println(i[0] + " " + i[1]);
ArrayList<board> children = board.addChild(Main.grid, i, disc, count);
count++;
}
//-------------------------
// ----------children list-----------;
//// for (int x = 0; x < board.getChildren().size(); x++){
//// System.out.println(board.getChildren().get(x));
//// }
// -------------------------
//-----------list scores----------
int maxIndex = 0;
int maxBoardVal = 0;
for (int i =0; i < board.getChildren().size() ; i++){
if (board.getChildren().get(i).getBoardVal() > maxBoardVal){
maxBoardVal = board.getChildren().get(i).getBoardVal();
maxIndex = i;
}
System.out.println(board.getChildren().get(i).getBoardVal()); //children only has 1 item in currently
}
//-------------------------
int[] coord = legalMoves.get(maxIndex);
int x = coord[0];
int y = coord[1];
Main.blackTurn = false; //set it to be white turn
System.out.println("minimax move select");
System.out.println(x);
System.out.println(y);
System.out.println("------");
Main.grid.setMove(new int[]{x, y}, 'w'); //need to change this from 'w' hardcoded to whichever AIs turn it is (both could be ai)
System.out.println("pick: " + x + " " + y);
gameHandler.flipDiscs('w','b',x,y, Main.grid.getBoard());
Main.blackTurn = !Main.blackTurn; //swap back to black (player) turn
//(this is only for one depth), for multiple depth we need to then make a list of all moves
//from each next ply (after accouting for every move that the opposite colour could make)
}
public class board {
private static char disc;
private static char[][] gameboard;
private static boolean blackTurn;
private static ArrayList<board> children;
private static int boardEval;
//private static board[] children;
//depth 0 = Main.grid
//depth 1 = Main.grid with a move made
//initializer to make first empty board
public board() {
gameboard = new char[8][8];
for (int i = 0; i < 8; i++){
for (int j = 0; j < 8; j++){
gameboard[i][j] = ' ';
}
}
}
public static void setGameboard(char[][] gameboard) {
board.gameboard = gameboard;
}
public static void setChildren(ArrayList<board> children) {
board.children = children;
}
public static ArrayList<board> getChildren(){
return children;
}
public char[][] getBoard(){
return gameboard;
}
public boolean getTurn(){
return blackTurn;
}
public static char getTile(int[] yo) {
char value = gameboard[yo[0]][yo[1]];
return value;
}
public static void setMove(int[] coord, char discColour){
gameboard[coord[0]][coord[1]] = discColour;
}
public static void printBoard(board grid){
//prints the logical grid in command line
for (int i =0; i< 8; i++){
for (int z =0; z< 8; z++){
System.out.print(grid.getBoard()[z][i]);
System.out.print("|");
}
System.out.println();
for (int q =0; q< 16; q++) {
System.out.print("-");
}
System.out.println();
}
System.out.println("end of grid");
}
public static void initialBoard(board grid){
for (int i = 0; i < 8; i++){
for (int j = 0; j < 8; j++){
int[] yo = {i, j};
board.setMove(yo, ' ');
}
}
board.setMove(new int[]{3, 3}, 'w');
board.setMove(new int[]{4, 3}, 'b');
board.setMove(new int[]{3, 4}, 'b');
board.setMove(new int[]{4, 4}, 'w');
}
public static int[] getScore(board board){
int blackScore = 0;
int whiteScore = 0;
for (int i = 0; i <= 7; i++){ //iterate over all tiles on board
for (int j = 0; j <= 7; j++) {
if (board.getTile(new int[]{i, j}) == 'b'){
blackScore++;
}
else if(board.getTile(new int[]{i, j}) == 'w'){
whiteScore++;
}
}
}
int score[] = {blackScore, whiteScore};
return score;
}
public static ArrayList<board> addChild(board board, int[] move, char discColour, int count{
board child = board;
ArrayList<board> children = board.getChildren();
child.setMove(move, discColour); //make the move on the child board
child.setBoardVal(MiniMax.evalFunction(board, Main.blackTurn)); //set value of child
children.add(child); //add child to list
board.setChildren(children); //set global list to local list
return children;
}
public static void setBoardVal(int boardVal){
boardEval = boardVal;
}
public static int getBoardVal(){
return boardEval;
}
For a project I am working on I would like to be abbe to "scroll" an array like so:
Here is the code I have so far:
private boolean[][] display = this.parseTo8BitMatrix("Here");
private int scroll = 0;
public void scroll() {
this.scroll++;
}
//sets the clock's display
public void setDisplay(String s) {
this.display = this.parseTo8BitMatrix(s);
}
//determines the current frame of the clock
private boolean[][] currentFrame() {
boolean[][] currentFrame = new boolean[8][32];
int length = this.display[0].length;
if(length == 32) { //do nothing
currentFrame = this.display;
} else if(length <= 24) { //center
for(int i = 0; i < 8; i++) {
for(int j = 0; j < length; j++) {
currentFrame[i][j+((32-length)/2)] = this.display[i][j];
}
}
this.display = currentFrame; //set display to currentFrame so the display doesn't get centered each time
} else { //scroll
for(int i = 0; i < 8; i++) {
for(int j = 0; j < length; j++) {
if(this.scroll+j <= 32) {
currentFrame[i][j] = this.display[i][j+this.scroll];
} else {
//?
}
}
}
}
return currentFrame;
}
The code I have is effective up until the the array needs to "wrap around" to the other side. Where have I gone wrong?
I'm assuming that you're looking for a formula that would work for the else.
Usually Modulos are very helpful for wrapping around.
What you are looking for is basically
currentFrame[i][j]= this.display[i][(j+this.scroll)%length];
which works even when it's not wrapped around.
I am attempting to make a random maze generator using Java and the recursive backtracking algorithm. I am getting stack overflow when I try to run this code. I know some about stack, I don't think this is infinite recursion. My guess is that I have a big logic error. Do I have to allocate more memory?
The stack trace:
Exception in thread "main" java.lang.StackOverflowError
at java.base/java.util.Vector.elementAt(Vector.java:499)
at java.base/java.util.Stack.peek(Stack.java:103)
at java.base/java.util.Stack.pop(Stack.java:84)
at mazeMaker.Maze.generateMaze(Maze.java:115)
at mazeMaker.Maze.generateMaze(Maze.java:115)
...
at mazeMaker.Maze.generateMaze(Maze.java:115)
at mazeMaker.Maze.generateMaze(Maze.java:115)
Main.java
package mazeMaker;
public class Main
{
public static void main(String[] args)
{
Maze mainMaze = new Maze(20, 30);
}
}
Maze.java
package mazeMaker;
import java.util.Random;
import java.util.Stack;
public class Maze
{
public int xSize = 0;
public int ySize = 0;
public int totalDimensions = 0;
Random randomGenerator = new Random();
public Cell[][] cellData;
public Stack<Cell> cellStack = new Stack<Cell>();
Cell tempCell; // Temporary variable used for maze generation
public Maze(int xSize, int ySize)
{
cellData = new Cell[xSize][ySize];
this.xSize = xSize;
this.ySize = ySize;
this.totalDimensions = this.xSize * this.ySize;
// Initialize array objects
for (int i = 0; i < this.xSize; i++)
{
for (int j = 0; j < this.ySize; j++)
{
cellData[i][j] = new Cell();
}
}
// Assign x and y positions
for (int i = 0; i < this.xSize; i++)
{
for (int j = 0; j < this.ySize; j++)
{
cellData[i][j].xPos = i;
cellData[i][j].yPos = j;
}
}
initBoundries();
generateMaze();
}
private void initBoundries()
{
// Initialize the border cells as visited so we don't go out of bounds
int m = this.xSize;
int n = this.ySize;
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
if (i == 0 || j == 0 || i == n - 1 || j == n - 1)
cellData[i][j].hasBeenVisited = true;
}
}
}
private void generateMaze(int x, int y)
{
// Set current cell as visited
cellData[x][y].hasBeenVisited = true;
// While there are unvisited neighbors
while (!cellData[x][y+1].hasBeenVisited || !cellData[x+1][y].hasBeenVisited || !cellData[x][y-1].hasBeenVisited || !cellData[x-1][y].hasBeenVisited)
{
// Select a random neighbor
while (true)
{
int r = randomGenerator.nextInt(4);
if (r == 0 && !cellData[x][y+1].hasBeenVisited)
{
cellStack.push(cellData[x][y]);
cellData[x][y].hasNorthWall = false;
cellData[x][y+1].hasSouthWall = false;
generateMaze(x, y + 1);
break;
}
else if (r == 1 && !cellData[x+1][y].hasBeenVisited)
{
cellStack.push(cellData[x][y]);
cellData[x][y].hasEastWall = false;
cellData[x+1][y].hasWestWall = false;
generateMaze(x+1, y);
break;
}
else if (r == 2 && !cellData[x][y-1].hasBeenVisited)
{
cellStack.push(cellData[x][y]);
cellData[x][y].hasSouthWall = false;
cellData[x][y-1].hasNorthWall = false;
generateMaze(x, y-1);
break;
}
else if (r == 3 && !cellData[x-1][y].hasBeenVisited)
{
cellStack.push(cellData[x][y]);
cellData[x][y].hasWestWall = false;
cellData[x-1][y].hasEastWall = false;
generateMaze(x-1, y);
break;
}
}
}
// There are no unvisited neighbors
tempCell = cellStack.pop();
generateMaze(tempCell.xPos, tempCell.yPos);
}
// Begin generating maze at top left corner
private void generateMaze()
{
generateMaze(1,1);
}
}
Cell.java
package mazeMaker;
public class Cell
{
public boolean isCurrentCell;
public boolean hasBeenVisited;
public boolean hasNorthWall;
public boolean hasSouthWall;
public boolean hasEastWall;
public boolean hasWestWall;
public int xPos;
public int yPos;
}
The method generateMaze can never terminate not even by chance for some simple reason:
For terminating the generateMaze method would need to finish it's execution - it has to return.
There are no return statements in this method, therefore it has to pass the while loops and then continue until the execution reaches and finishes the last statement of the method.
However the last statement is generateMaze(tempCell.xPos, tempCell.yPos); which starts a new recursion, therefore your code can never ever terminate!
I tried to run your project on my own environment but unfortunately, I was not able to reproduce your issue.
However, I was facing an IndexOutOfBound exception in the method generateMaze. While I was solving this, I figured out that there was an issue in the initBoudaries method.
Indeed, when you set the boolean hasBeenVisited to true, you do not use the right variable in the IF clause. Here is the version I tried instead :
private void initBoundries()
{
// Initialize the border cells as visited so we don't go out of bounds
for (int i = 0; i < this.xSize; i++)
{
for (int j = 0; j < ySize; j++)
{
if (i == 0 || j == 0 || i == xSize - 1 || j == ySize - 1)
cellData[i][j].hasBeenVisited = true;
}
}
}
Now about the emptyStackException, I think that if this stack is empty, this means that there is no more cell to handle (as you mentioned in your comment) and the program must end. If I am right, just make sure to test if your stack is empty before call the method pop() on it like this :
// There are no unvisited neighbors
if (!cellStack.isEmpty()) {
tempCell = cellStack.pop();
generateMaze(tempCell.xPos, tempCell.yPos);
}
Hope it will help.
I am making a game where move across a gridlayout of jbuttons, only allowing movement to adjacent grid jbuttons. The goal is to try and make it to the topleft (From the btm right). I think this is all that is relevant:
//Here is my method to determine whether it is a valid move (i dont want you to be able to move anywhere but adjacent spots
public boolean goodMove(int x, int y) {
int val = Math.abs(current.x - x) + Math.abs(current.y - y);
return val == 1;
}
//Here is the logic in the actionlister, a big if else loop that is now referencing only the jbuttons in my gridlayout (the game panel)
public void actionPerformed(ActionEvent e) {
JButton clicked = (JButton) e.getSource();
if (clicked == SHOWMINES) {
if (SHOWMINES.getText().equals("Show Mines")) {
showMines();
SHOWMINES.setText("Hide Mines");
} else {
hideMines();
}
} else {
if (clicked == NEWGAME) {
newGame();
} else {
if (clicked == SHOWPATH) {
if (SHOWPATH.getText().equals("Show Path")) {
showPath();
SHOWPATH.setText("Hide Path");
} else {
hidePath();
}
} else {
if (clicked == OKAY) {
resetGridSize(Integer.parseInt(gridSizeInput.getText()));
newGame();
} else {
}
int gs = Integer.parseInt(gridSizeInput.getText());
for (int i = 0; i < gs - 1; i++) {
for (int j = 0; j < gs - 1; j++) {
if (clicked == grid[i][j]) {
if (goodMove(i, j)) {//debug tester ,this is where the problem is.
System.out.println("TEST");
current = new Point(i, j);
grid[i][j].setBackground(Color.green);
grid[i][j].setText("=");
}else{System.out.println("TEST2");}
}
}
}
}
}
}
}// end of action listener
Grid[][] is my array of grid buttons, gridSizeInput is the textfield that you can adjust the gridsize in.
Thanks in advance (I'm new to programming, so keep it simple if you can :)
EDIT: I forgot to mention, it is not recognizing my if(goodMove()) loop, its just printing out TWO for my else.
EDIT: My question is why is not working, no buttons will are be recognized as clicked in my grid, I guess I am asking to see if something I have written is glaringly obviously wrong. thanks.
EDIT: Okay here is my entire action listener for all my buttons. This is my first post, sorry I am not more helpful- let me know if anything else I can add.
Your problem may be as simple as your not extending your for loops out far enough. Try changing this:
for (int i = 0; i < gs - 1; i++) {
for (int j = 0; j < gs - 1; j++) {
to this:
for (int i = 0; i < gs; i++) {
for (int j = 0; j < gs; j++) {
Edit
This was the minimal code example that I created to test the concept:
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.*;
import javax.swing.*;
public class Foo extends JPanel {
public static final int GRID_SIZE = 10;
private Point current;
private JButton[][] grid;
public Foo() {
ButtonListener listener = new ButtonListener();
setLayout(new GridLayout(GRID_SIZE, GRID_SIZE));
grid = new JButton[GRID_SIZE][GRID_SIZE];
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[i].length; j++) {
grid[i][j] = new JButton(" ");
grid[i][j].addActionListener(listener);
add(grid[i][j]);
}
}
current = new Point(GRID_SIZE - 1, GRID_SIZE - 1);
}
private class ButtonListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
JButton clicked = (JButton) e.getSource();
// !! int gs = Integer.parseInt(gridSizeInput.getText());
int gs = GRID_SIZE;
// !! for (int i = 0; i < gs - 1; i++) {
// for (int j = 0; j < gs - 1; j++) {
for (int i = 0; i < gs; i++) {
for (int j = 0; j < gs; j++) {
if (clicked == grid[i][j]) {
if (goodMove(i, j)) {
System.out.println("TEST");
current = new Point(i, j);
grid[i][j].setBackground(Color.green);
grid[i][j].setText("=");
} else {
System.out.println("TEST2");
}
}
}
}
}
}
public boolean goodMove(int x, int y) {
int val = Math.abs(current.x - x) + Math.abs(current.y - y);
return val == 1;
}
private static void createAndShowGui() {
Foo mainPanel = new Foo();
JFrame frame = new JFrame("Foo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
I am working on Conway's Game of Life for a school project. I am not looking for the code directly. I am looking to find out what is wrong with my code.
In Conway's Game of Life a cell goes from dead to alive if it has 3 alive neighbors. It stays alive if it has two or three alive neighbors. If none of those are true it is dead.
My LifeView class has a method that displays the cell simulation and afterwards displays how many alive cells are around the given point.
This is the output I am getting:
How many rows is your simulation?
5
How many columns is your simulation?
5
How many generations is your simulation?
3
xxxxx
xxxxx
xx0xx
xx0xx
xx0xx
00000
01110
02120
03230
02120
xxxxx
xxxxx
xxxxx
xxxxx
xxxxx
00000
00000
00000
00000
00000
xxxxx
xxxxx
xxxxx
xxxxx
xxxxx
00000
00000
00000
00000
00000
This is wrong because the second generation is supposed to be a horizontal line of live cells crossing the center of the first generation alive cells. Instead of crossing that center, all cells are turned dead. I am stumped as to why it doesn't work.
Main class:
package gameOfLife;
import java.util.Scanner;
public class Main {
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
Scanner numReader = new Scanner(System.in);
System.out.println("How many rows is your simulation?");
int rows = numReader.nextInt();
System.out.println("How many columns is your simulation?");
int columns = numReader.nextInt();
System.out.println("How many generations is your simulation?");
int generations = numReader.nextInt();
LifeModel model = new LifeModel(rows,columns);
LifeView life = new LifeView(model);
for(int i=0; i<generations; i++)
{
life.displayLife();
model.nextGeneration();
}
}
LifeView class:
package gameOfLife;
import java.util.Scanner;
public class LifeView {
private LifeModel model;
public LifeView(LifeModel model)
{
this.model = model;
}
public void displayLife()
{
for(int i=0; i < model.getWorld().length; i++)
{
for(int j=0; j < model.getWorld()[0].length; j++)
{
if(model.getWorld()[i][j])
{
System.out.print("0");
}
else
{
System.out.print("x");
}
}
System.out.println("");
}
System.out.println("");
for(int i=0; i < model.getWorld().length; i++)
{
for(int j=0; j < model.getWorld()[0].length; j++)
{
System.out.print(model.numLivingNeighbors(i,j));
}
System.out.println("");
}
System.out.println("");
System.out.println("");
}
}
LifeModel class:
package gameOfLife;
public class LifeModel
{
private boolean[][] world;
private int numRows;
private int numCols;
private boolean[][] tempWorld;
public LifeModel(int rows, int cols)
{
this.numRows=rows;
this.numCols=cols;
world = new boolean[rows][cols];
initWorld();
tempWorld = world;
}
private void initWorld()
{
boolean done = false;
while(!done)
{
int i = (int) (Math.random()*numRows);
int j = (int) (Math.random()*numCols);
if(j>0 && i>0 && i<numRows-1 && j<numCols-1)
{
/*
world[i-1][j-1] = true;
world[i-1][j] = true;
world[i-1][j+1] = true;
world[i][j+1] = true;
world[i+1][j] = true;
*/
world[i][j]=true;
world[i+1][j]=true;
world[i-1][j]=true;
done = true;
}
}
}
public void nextGeneration()
{
//tempWorld = new boolean[numRows+2][numCols+2];
int rows = world.length;
int columns = world[0].length;
for(int i=0; i < rows; i++)
{
for(int j = 0; j < columns; j++)
{
toggleCell(i,j);
}
}
world = tempWorld;
}
public void toggleCell(int r, int c)
{
int count = numLivingNeighbors(r,c);
if(!world[r][c] && count==3)
{
tempWorld[r][c] = true;
}
else if(world[r][c] && (count>=2 && count<=3))
{
tempWorld[r][c] = true;
}
else
{
tempWorld[r][c] = false;
}
}
public int numLivingNeighbors(int r, int c)
{
int count = 0;
boolean newCells[][] = world;
for(int i = -1; i<=1; i++)
{
for(int j = -1; j<=1; j++)
{
if(i!=0 || j!=0)
{
int row = r + i;
int column = c + j;
if(row>=0 && row < newCells.length && column>=0 && column<newCells[0].length && newCells[row][column])
{
count++;
}
}
}
}
return count;
}
public void userChange()
{
}
public boolean[][] getWorld()
{
return world;
}
}
Any help is GREATLY appreciated!
You just have a couple small issues with your LifeModel class.
In your constructor you set the tempWorld to reference the same array as the actual game world. This will cause any modifications to tempWorld to also affect the gameWorld.
public LifeModel(int rows, int cols)
{
this.numRows=rows;
this.numCols=cols;
world = new boolean[rows][cols];
initWorld();
//tempWorld = world; // You can remove this line.
}
Then in next generation you have the line "//tempWorld = new boolean[numRows+2][numCols+2];" commented out. You really do need to create a new temp array here so you aren't changing the game board as you read it. However, I'm not sure what the +2 is supposed to be, so I removed it. You should have:
public void nextGeneration()
{
tempWorld = new boolean[numRows][numCols]; // Keep it the same size
int rows = world.length;
int columns = world[0].length;
for(int i=0; i < rows; i++)
{
for(int j = 0; j < columns; j++)
{
toggleCell(i,j);
}
}
world = tempWorld;
}
After I made those changes it worked perfectly for me. I've included the full LifeModel class below that I used on my machine.
package gameOfLife;
public class LifeModel
{
private boolean[][] world;
private int numRows;
private int numCols;
private boolean[][] tempWorld;
public LifeModel(int rows, int cols)
{
this.numRows=rows;
this.numCols=cols;
world = new boolean[rows][cols];
initWorld();
}
private void initWorld()
{
boolean done = false;
while(!done)
{
int i = (int) (Math.random()*numRows);
int j = (int) (Math.random()*numCols);
if(j>0 && i>0 && i<numRows-1 && j<numCols-1)
{
/*
world[i-1][j-1] = true;
world[i-1][j] = true;
world[i-1][j+1] = true;
world[i][j+1] = true;
world[i+1][j] = true;
*/
world[i][j]=true;
world[i+1][j]=true;
world[i-1][j]=true;
done = true;
}
}
}
public void nextGeneration()
{
tempWorld = new boolean[numRows][numCols];
int rows = world.length;
int columns = world[0].length;
for(int i=0; i < rows; i++)
{
for(int j = 0; j < columns; j++)
{
toggleCell(i,j);
}
}
world = tempWorld;
}
public void toggleCell(int r, int c)
{
int count = numLivingNeighbors(r,c);
if(!world[r][c] && count==3)
{
tempWorld[r][c] = true;
}
else if(world[r][c] && (count>=2 && count<=3))
{
tempWorld[r][c] = true;
}
else
{
tempWorld[r][c] = false;
}
}
public int numLivingNeighbors(int r, int c)
{
int count = 0;
boolean newCells[][] = world;
for(int i = -1; i<=1; i++)
{
for(int j = -1; j<=1; j++)
{
if(i!=0 || j!=0)
{
int row = r + i;
int column = c + j;
if(row>=0 && row < newCells.length && column>=0 && column<newCells[0].length && newCells[row][column])
{
count++;
}
}
}
}
return count;
}
public void userChange()
{
}
public boolean[][] getWorld()
{
return world;
}
}
Check that numLivingNeighbors is returning the proper value for each cell in the world.
Also check the step for staying alive
Hey You have done a simple mistake in your code
public LifeModel(int rows, int cols)
{
this.numRows=rows;
this.numCols=cols;
world = new boolean[rows][cols];
initWorld();
tempWorld = world;
}
this is LifeModel constructor.
In this constructor you need to initialize tempworld also. You should not assign your world to tempworld.
After modification this block of code will become like this....
public LifeModel(int rows, int cols)
{
this.numRows=rows;
this.numCols=cols;
world = new boolean[rows][cols];
tempWorld = new boolean[rows][cols];
initWorld();
}
After this your output will be correct.