first commit
This commit is contained in:
366
Hjemmeopgaver/Hjemmeopgave nr 3/src/GameOfLifeMain.java
Normal file
366
Hjemmeopgaver/Hjemmeopgave nr 3/src/GameOfLifeMain.java
Normal file
@@ -0,0 +1,366 @@
|
||||
import javafx.scene.input.KeyCode;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Scanner;
|
||||
import java.util.Random;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Game of life class.
|
||||
*
|
||||
* @author rasmus
|
||||
* @version 4.2
|
||||
*/
|
||||
public class GameOfLifeMain {
|
||||
/**
|
||||
* The Is periodic.
|
||||
*/
|
||||
static boolean isPeriodic = false;
|
||||
/**
|
||||
* The Auto run.
|
||||
*/
|
||||
static boolean autoRun = false;
|
||||
/**
|
||||
* The Key pressed.
|
||||
*/
|
||||
static boolean keyPressed = false;
|
||||
/**
|
||||
* The Board size.
|
||||
*/
|
||||
static int boardSize;
|
||||
/**
|
||||
* The Text position.
|
||||
*/
|
||||
static double[] textPosition;
|
||||
/**
|
||||
* The Jump to iteration.
|
||||
*/
|
||||
static int jumpToIteration = -1; // When it's < 0, it is to be ignored.
|
||||
|
||||
/**
|
||||
* The entry point of application.
|
||||
*
|
||||
* @param args the input arguments
|
||||
* @throws IOException the io exception
|
||||
*/
|
||||
public static void main(String[] args) throws IOException {
|
||||
StdDraw.setCanvasSize(1000, 1050);
|
||||
Scanner console = new Scanner(System.in);
|
||||
System.out.print("Please enter the size of the board (enter -1 to input file that should be imported): ");
|
||||
boardSize = console.nextInt();
|
||||
GameOfLife gameOfLife = new GameOfLife(Math.max(boardSize, 1));
|
||||
if (boardSize < 0) {
|
||||
System.out.print("Please enter path of file: ");
|
||||
String filePath = console.next();
|
||||
File file = new File(filePath);
|
||||
gameOfLife = importFromFile(file);
|
||||
boardSize = gameOfLife.size;
|
||||
}
|
||||
textPosition = new double[] {10.0 / 1000 * boardSize,(Math.ceil(boardSize * 1.05)-boardSize)/2+boardSize};
|
||||
StdDraw.setPenRadius(1.3 / (boardSize + 1));
|
||||
StdDraw.show(0);
|
||||
StdDraw.setXscale(-1, boardSize);
|
||||
StdDraw.setYscale(-1, (int) Math.ceil(boardSize*1.05));
|
||||
StdDraw.clear();
|
||||
gameOfLife.draw();
|
||||
StdDraw.textLeft(textPosition[0], textPosition[1], "Iteration nr.: " + gameOfLife.iteration + " Period is not found yet.");
|
||||
StdDraw.show(0);
|
||||
while (true) {
|
||||
if (!keyPressed && jumpToIteration < 0) {
|
||||
if (StdDraw.isKeyPressed(39)) { // Right arrow key is pressed. Go forward 1 iteration
|
||||
runIteration(gameOfLife);
|
||||
keyPressed = true;
|
||||
} else if (StdDraw.isKeyPressed(37) && gameOfLife.iteration > 0) { // Left arrow key is pressed. Go back 1 iteration
|
||||
gameOfLife.previousState();
|
||||
StdDraw.clear();
|
||||
gameOfLife.draw();
|
||||
StdDraw.textLeft(textPosition[0], textPosition[1], "Iteration nr.: " + gameOfLife.iteration +
|
||||
(isPeriodic ? " Period is: " + gameOfLife.period : " Period is not found yet"));
|
||||
StdDraw.show(0);
|
||||
keyPressed = true;
|
||||
} else if (StdDraw.isKeyPressed(32)) { // If space is pressed. Autoruns or pauses game
|
||||
autoRun = !autoRun;
|
||||
keyPressed = true;
|
||||
} else if (StdDraw.isKeyPressed(73)) { // If "i" is pressed. Goes to inputted iteration.
|
||||
/*
|
||||
When a iteration nr. is given to be jumped to. The simulation will run in the background not updating the board
|
||||
until it reaches that iteration, in which case it will pause running and update board again.
|
||||
*/
|
||||
System.out.print("Enter iteration to jump to: ");
|
||||
jumpToIteration = Integer.parseInt(console.nextLine());
|
||||
StdDraw.setPenRadius(50);
|
||||
StdDraw.text((double) (boardSize + 1) /2, (double) ((int) Math.ceil(boardSize * 1.05) + 1) /2, "RUNNING SIMULATION. WAIT");
|
||||
StdDraw.show(0);
|
||||
StdDraw.setPenRadius(1.3 / (boardSize + 1));
|
||||
autoRun = true;
|
||||
keyPressed = true;
|
||||
} else if (StdDraw.isKeyPressed(83)) { // If "s" is pressed. Saves board
|
||||
System.out.print("Enter filename (and path): ");
|
||||
String savedFile = console.next();
|
||||
gameOfLife.exportStateToFile(savedFile);
|
||||
}
|
||||
} else if (!StdDraw.isKeyPressed(39) && !StdDraw.isKeyPressed(37) && !StdDraw.isKeyPressed(32) && !StdDraw.isKeyPressed(73)) {
|
||||
// This is to make sure, that when a key is pressed, it is only acted upon once until it is pressed again.
|
||||
keyPressed = false;
|
||||
}
|
||||
if (autoRun) {
|
||||
runIteration(gameOfLife);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run iteration.
|
||||
*
|
||||
* @param gameOfLife the game of life
|
||||
*/
|
||||
public static void runIteration(GameOfLife gameOfLife) {
|
||||
gameOfLife.nextState();
|
||||
int periodic = (isPeriodic ? -1 : gameOfLife.checkPeriodic()); // Only check if board is periodic if it hasnt already been found to be.
|
||||
if (periodic >= 0) {
|
||||
int period = gameOfLife.iteration - periodic;
|
||||
System.out.println("The simulation is periodic. The period is: " + period);
|
||||
isPeriodic = true;
|
||||
autoRun = gameOfLife.iteration <= jumpToIteration; // Game should autoRun if iteration nr. is below iteration nr. it should run until.
|
||||
}
|
||||
if (gameOfLife.iteration >= jumpToIteration) {
|
||||
StdDraw.clear();
|
||||
gameOfLife.draw();
|
||||
StdDraw.textLeft(textPosition[0], textPosition[1], "Iteration nr.: " + gameOfLife.iteration +
|
||||
(isPeriodic ? " Period is: " + gameOfLife.period : " Period is not found yet"));
|
||||
StdDraw.show(0);
|
||||
if (gameOfLife.iteration == jumpToIteration) {
|
||||
jumpToIteration = -1;
|
||||
autoRun = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Import from file game of life.
|
||||
*
|
||||
* @param file the file
|
||||
* @return the game of life
|
||||
* @throws FileNotFoundException the file not found exception
|
||||
*/
|
||||
public static GameOfLife importFromFile(File file) throws FileNotFoundException {
|
||||
/*
|
||||
Imports a simulation-state from a file with example file format:
|
||||
0 1 0 0
|
||||
1 0 0 0
|
||||
1 1 1 1
|
||||
0 0 1 1
|
||||
First goes through first line of file (and defines length) and the rest of the lines. Returns a GameOfLife object with imported board.
|
||||
*/
|
||||
Scanner importedFile = new Scanner(file);
|
||||
String firstLine = importedFile.nextLine();
|
||||
String[] firstLineSplit = firstLine.split(" ");
|
||||
int length = firstLineSplit.length;
|
||||
int[][] grid = new int[length][length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
grid[0][i] = Integer.parseInt(firstLineSplit[i]);
|
||||
}
|
||||
int rowNum = 1;
|
||||
while (importedFile.hasNextLine()) {
|
||||
String Line = importedFile.nextLine();
|
||||
String[] lineSplit = Line.split(" ");
|
||||
for (int i = 0; i < length; i++) {
|
||||
grid[rowNum][i] = Integer.parseInt(lineSplit[i]);
|
||||
}
|
||||
rowNum++;
|
||||
}
|
||||
importedFile.close();
|
||||
return new GameOfLife(grid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The type Game of life.
|
||||
*/
|
||||
class GameOfLife {
|
||||
private int[][] state;
|
||||
/**
|
||||
* The Size.
|
||||
*/
|
||||
int size;
|
||||
/**
|
||||
* The Iteration.
|
||||
*/
|
||||
int iteration = 0;
|
||||
/**
|
||||
* The Period.
|
||||
*/
|
||||
int period = -1;
|
||||
/**
|
||||
* The First periodic state.
|
||||
*/
|
||||
int firstPeriodicState; // The index of the state when the simulations periodic nature begins.
|
||||
/**
|
||||
* The Previous boards list.
|
||||
*/
|
||||
ArrayList<int[][]> previousBoardsList = new ArrayList<>(); // A list containing all the previous arrays of this.state.
|
||||
/**
|
||||
* The Previous boards list hash.
|
||||
*/
|
||||
ArrayList<Integer> previousBoardsListHash = new ArrayList<>(); // A list containing all the previous array-hashes of this.state. Both lists used for checking if periodic
|
||||
|
||||
/**
|
||||
* Instantiates a new Game of life.
|
||||
*
|
||||
* @param n the n
|
||||
*/
|
||||
public GameOfLife(int n) {
|
||||
/*
|
||||
Create a random board of 1's and 0's based on array size n * n
|
||||
*/
|
||||
Random rand = new Random();
|
||||
this.size = n;
|
||||
this.state = new int[n][n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
for (int j = 0; j < n; j++) {
|
||||
this.state[i][j] = rand.nextInt(2);
|
||||
}
|
||||
}
|
||||
this.previousBoardsList.add(this.state);
|
||||
this.previousBoardsListHash.add(Arrays.deepHashCode(this.state));
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new Game of life.
|
||||
*
|
||||
* @param initialState the initial state
|
||||
*/
|
||||
public GameOfLife(int[][] initialState) {
|
||||
this.state = initialState;
|
||||
this.size = initialState.length;
|
||||
this.previousBoardsList.add(this.state);
|
||||
this.previousBoardsListHash.add(Arrays.deepHashCode(this.state));
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return Arrays.deepToString(this.state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw.
|
||||
*/
|
||||
public void draw() {
|
||||
for (int i = 0; i < state.length; i++) {
|
||||
for (int j = 0; j < state[i].length; j++) {
|
||||
if (this.state[i][j] == 1) {
|
||||
StdDraw.point(j, this.size-i-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Next state.
|
||||
*/
|
||||
public void nextState() {
|
||||
/*
|
||||
Goes through the current state-array and builds/calculates next state. Also adds current state and state-hash to lists
|
||||
*/
|
||||
int[][] newState = new int[this.size][this.size];
|
||||
for (int i = 0; i < state.length; i++) {
|
||||
for (int j = 0; j < state[i].length; j++) {
|
||||
int aliveNeighbors = this.aliveNeighbours(i, j);
|
||||
if (this.state[i][j] == 1 && (aliveNeighbors < 2 || aliveNeighbors > 3)) { // If current cell is alive, and it should die of boredom or overcrowding
|
||||
newState[i][j] = 0;
|
||||
} else if (this.state[i][j] == 0 && aliveNeighbors == 3) { // Dead cell with 3 neighbors returns to life
|
||||
newState[i][j] = 1;
|
||||
} else {
|
||||
newState[i][j] = this.state[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
this.previousBoardsList.add(this.state);
|
||||
this.previousBoardsListHash.add(Arrays.deepHashCode(this.state));
|
||||
this.state = newState;
|
||||
this.iteration++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Previous state.
|
||||
*/
|
||||
public void previousState() {
|
||||
/*
|
||||
Makes current state the previous one, and removes it from the lists
|
||||
*/
|
||||
this.state = this.previousBoardsList.getLast();
|
||||
this.previousBoardsList.removeLast();
|
||||
this.previousBoardsListHash.removeLast();
|
||||
this.iteration--;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alive neighbours int.
|
||||
*
|
||||
* @param x the x
|
||||
* @param y the y
|
||||
* @return the int
|
||||
*/
|
||||
public int aliveNeighbours(int x, int y) {
|
||||
/*
|
||||
Returns the amount of alive neighbors. It does this by adding the values of the surrounding squares.
|
||||
In easier to read format, its built like this:
|
||||
If not at the top of the array:
|
||||
Add together:
|
||||
Value of above left square, if not at the left edge of array, else 0
|
||||
Value of square above
|
||||
Value of above right square, if not at top right edge of array, else 0
|
||||
Else: 0
|
||||
And then the sum of that plus the same for left and right squares and square below.
|
||||
*/
|
||||
return (y > 0 ? (x > 0 ? this.state[x-1][y-1] : 0) + this.state[x][y-1] + (x < this.size - 1 ? this.state[x+1][y-1] : 0) : 0) + // Neighbors above
|
||||
(x > 0 ? this.state[x-1][y] : 0) + (x < this.size - 1 ? this.state[x+1][y] : 0) + // Neighbors on same row
|
||||
(y < this.size - 1 ? (x > 0 ? this.state[x-1][y+1] : 0) + this.state[x][y+1] + (x < this.size - 1 ? this.state[x+1][y+1] : 0) : 0); // Neighbors below
|
||||
}
|
||||
|
||||
/**
|
||||
* Check periodic int.
|
||||
*
|
||||
* @return the int
|
||||
*/
|
||||
public int checkPeriodic() {
|
||||
/*
|
||||
Uses the indexOf array-function to search for matches in the list of array-state-hashes, returns > 0 if theres a match.
|
||||
If there's a match, compares the arrays using Array-function deepEquals to make sure arrays match as only using
|
||||
hashes can lead to false positives.
|
||||
*/
|
||||
int matches = this.previousBoardsListHash.indexOf(Arrays.deepHashCode(this.state));
|
||||
if (matches >= 0 && Arrays.deepEquals(this.previousBoardsList.get(matches), this.state)) {
|
||||
this.period = this.iteration - matches;
|
||||
this.firstPeriodicState = matches;
|
||||
return matches;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export state to file.
|
||||
*
|
||||
* @param filename the filename
|
||||
* @throws IOException the io exception
|
||||
*/
|
||||
public void exportStateToFile(String filename) throws IOException {
|
||||
/*
|
||||
Goes through this.state array and saves it to a file. Example format its saved as:
|
||||
0 1 0 0
|
||||
1 0 0 0
|
||||
1 1 1 1
|
||||
0 0 1 1
|
||||
*/
|
||||
FileWriter fileName = new FileWriter(filename);
|
||||
PrintWriter writer = new PrintWriter(fileName);
|
||||
for (int i = 0; i < this.state.length; i++) {
|
||||
for (int j = 0; j < this.state[i].length; j++) {
|
||||
writer.print(this.state[i][j] + (j < this.state[i].length - 1 ? " " : ""));
|
||||
}
|
||||
writer.println();
|
||||
}
|
||||
writer.close();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user