all the code in a single file :)
/*
Conway's Game Of Life
Brandon Hernandez
2023-02-11 (approx.)
*/
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Files;
import java.util.InputMismatchException;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
// Scanner for the input stream. Delimiter is white-space by default.
Scanner scanner = new Scanner(System.in);
String message = "Welcome.";
int rows = 0;
int cols = 0;
int arg0 = 0;
//If an argument was given from the CLI.
if (args.length > 0) {
try {
arg0 = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
message = "Cannot create map from given arguments!";
}
}
if (arg0 <= 0) {
boolean mapErr = true;
while (mapErr) {
ClearCLI();
PrintHeader();
PrintMessage(message);
PrintMessage("Create your map:");
// Input like this: 10 10
System.out.printf("Rows Columns --> ");
try {
rows = scanner.nextInt();
cols = scanner.nextInt();
// Clear the scanner buffer
scanner.nextLine();
if (rows == 0 || cols == 0) {
throw new InputMismatchException();
}
mapErr = false;
message = "Map created.";
} catch (Exception e) {
//^^Generic Exception to also catch the one thrown by CTRL+C (abort CLI program).
message = "Error: Invalid input.";
// Clear the scanner buffer, or else eternal invalid input.
scanner.nextLine();
}
}
} else {
// Arguments always create a square map!
rows = arg0;
cols = rows;
}
// Map init
boolean[][] map = new boolean[rows][cols];
message = String.format("%dx%d map created.", rows, cols);
int menuOpt = 0;
// Game attributes (make this a class!)
int maxGens = 10;
int refreshRate = 100;
boolean mapOverwrite = false;
while (menuOpt != 99) {
// Menu
ClearCLI();
PrintHeader();
PrintMap(map, true, true);
//Last action message, or input error message.
PrintMessage(message);
/*
Improvements
-[DONE] Create blank map option. Set dimensions.
-Allow game abort.
-Create random map option. Set dimensions.
-How many spaceships?
-How many oscillators?
-Create spaceship option.
-Create default shape option.
-Create oscillator option.
-End game when everything is dead, only oscillators, or only still lifes.
-Generate statistics and insights. (This map turned into that map after x generations, and so on).
-Input filename for save and load.
-Develop further in Rust.
*/
System.out.printf("%n%s %s %s %s %s %s %s %s %s %s%n%s",
"1. Create/kill cell",
"2. Set generations",
"3. Set refresh rate",
"4. Play",
"5. Save map",
"6. Load map",
"7. Random map",
"8. Final map overwrite",
"9. Create map",
"99. Exit",
"Your choice: "
);
try {
menuOpt = scanner.nextInt();
scanner.nextLine();
} catch (InputMismatchException e) {
message = String.format("Error: Invalid input.");
scanner.nextLine();
// Do not execute the code after this. Skip to the next cycle.
continue;
}
switch (menuOpt) {
case 1:
// Create/Kill Cell
message = CellBirthOrDeath(scanner, map);
break;
case 2:
// Generations
System.out.printf("Generations --> ");
try {
maxGens = scanner.nextInt();
scanner.nextLine();
message = String.format("Generations = %d", maxGens);
} catch (InputMismatchException e) {
message = "Invalid input.";
}
break;
case 3:
// Refresh rate
System.out.printf("Refresh rate ms --> ");
try {
refreshRate = scanner.nextInt();
scanner.nextLine();
message = String.format("Refresh rate is %d ms", refreshRate);
} catch (InputMismatchException e) {
message = "Invalid input.";
}
break;
case 4:
// Play
if (mapOverwrite) {
map = Play(map, maxGens, refreshRate);
}
else {
Play(map, maxGens, refreshRate);
}
message = "Game finished.";
break;
case 5:
//Save
try {
SaveMap("maps\\map.txt", map);
message = "Map saved.";
} catch (IOException e) {
message = "Error saving map.";
}
break;
case 6:
//Load
String filename = String.format("maps\\%s.txt", "map");
try {
map = LoadMap(filename);
message = String.format("%s map loaded.", filename);
} catch (Error | IOException | ArrayIndexOutOfBoundsException e) {
message = String.format("Error loading map %s", filename);;
}
break;
case 7:
//Random
message = "Random map function under development!";
break;
case 8:
mapOverwrite = !mapOverwrite;
message = mapOverwrite ? "Map Overwrite Enabled" : "Map Overwrite Disabled";
break;
case 9:
// Create map
System.out.printf("Create map. Rows Columns --> ");
try {
rows = scanner.nextInt();
cols = scanner.nextInt();
// Clear the scanner buffer
scanner.nextLine();
if (rows == 0 || cols == 0) {
throw new InputMismatchException();
}
message = "Map created.";
map = new boolean[rows][cols];
} catch (Exception e) {
//^^Generic Exception to also catch the one thrown by CTRL+C (abort CLI program).
message = "Error: Invalid input.";
// Clear the scanner buffer, or else eternal invalid input.
scanner.nextLine();
}
break;
case 99:
// Exit
Bye();
break;
default:
// Default
message = "Invalid option.";
break;
}
}
scanner.close();
}
public static String CellBirthOrDeath (Scanner scanner, boolean[][] map) {
int r;
int c;
System.out.printf("Where? Row Col --> ");
try {
r = scanner.nextInt();
c = scanner.nextInt();
scanner.nextLine();
} catch (InputMismatchException e) {
//e.printStackTrace();
//Throw away bad input in scanner buffer.
scanner.nextLine();
return String.format("Error: Invalid input.");
}
if (r >= map.length || r < 0) {
return String.format("Error: Row value %d is out of bounds. Min = 0, Max = %d.", r, map.length - 1);
}
if (c >= map[0].length || c < 0) {
return String.format("Error: Column value %d is out of bounds. Min = 0, Max = %d.", c, map[0].length - 1);
}
map[r][c] = !map[r][c];
if (map[r][c]) {
return String.format("[■ ] New cell at [%2d][%2d]", r, c);
}
return String.format("[ ] Killed cell at [%2d][%2d]", r, c);
}
public static boolean[][] Play(boolean[][] map, int maxGens, int refreshRate) {
int neighbors;
int generations = 0;
int iSize = map.length;
int jSize = map[0].length;
int iLast = iSize - 1;
int jLast = jSize - 1;
int iIndex = 0;
int jIndex = 0;
ClearCLI();
PrintHeader();
PrintMap(map, false, false);
System.out.printf("Generation: %d/%d", generations, maxGens);
Wait(refreshRate);
while (generations < maxGens) {
boolean[][] nextMap = new boolean[iSize][jSize];
for (int i = 0; i < iSize; i++) {
for (int j = 0; j < jSize; j++) {
neighbors = 0;
/*
[i-1,j-1] [i-1,_j_] [i-1,j+1]
[_i_,j-1] [_i_,_j_] [_i_,j+1]
[i+1,j-1] [i+1,_j_] [i+1,j+1]
[i-1,j-1] : NW
[i-1,_j_] : N
[i-1,j+1] : NE
[_i_,j-1] : W
[_i_,j+1] : E
[i+1,j-1] : SW
[i+1,_j_] : S
[i+1,j+1] : SE
*/
// [i-1,j-1] : NW
iIndex = i - 1;
jIndex = j - 1;
if (iIndex < 0) {
iIndex = iLast;
}
if (jIndex < 0) {
jIndex = jLast;
}
neighbors = map[iIndex][jIndex] ? neighbors + 1 : neighbors;
// [i-1,_j_] : N
iIndex = i - 1;
jIndex = j;
if (iIndex < 0) {
iIndex = iLast;
}
neighbors = map[iIndex][jIndex] ? neighbors + 1 : neighbors;
// [i-1,j+1] : NE
iIndex = i - 1;
jIndex = j + 1;
if (iIndex < 0) {
iIndex = iLast;
}
if (jIndex > jLast) {
jIndex = 0;
}
neighbors = map[iIndex][jIndex] ? neighbors + 1 : neighbors;
// [_i_,j+1] : W
iIndex = i;
jIndex = j + 1;
if (jIndex > jLast) {
jIndex = 0;
}
neighbors = map[iIndex][jIndex] ? neighbors + 1 : neighbors;
// [_i_,j-1] : E
iIndex = i;
jIndex = j - 1;
if (jIndex < 0) {
jIndex = jLast;
}
neighbors = map[iIndex][jIndex] ? neighbors + 1 : neighbors;
// [i+1,j-1] : SW
iIndex = i + 1;
jIndex = j - 1;
if (iIndex > iLast) {
iIndex = 0;
}
if (jIndex < 0) {
jIndex = jLast;
}
neighbors = map[iIndex][jIndex] ? neighbors + 1 : neighbors;
// [i+1,_j_] : S
iIndex = i + 1;
jIndex = j;
if (iIndex > iLast) {
iIndex = 0;
}
neighbors = map[iIndex][jIndex] ? neighbors + 1 : neighbors;
// [i+1,j+1] : SE
iIndex = i + 1;
jIndex = j + 1;
if (iIndex > iLast) {
iIndex = 0;
}
if (jIndex > jLast) {
jIndex = 0;
}
neighbors = map[iIndex][jIndex] ? neighbors + 1 : neighbors;
// Create next generation
// 1. Any live cell with fewer than 2 live neighbors dies, as if by underpopulation.
if (map[i][j] && neighbors < 2) {
nextMap[i][j] = false;
}
// 2. Any live cell with 2 or 3 live neighbors lives on to the next generation.
if (map[i][j] && (neighbors == 2 || neighbors == 3)) {
nextMap[i][j] = true;
}
// 3. Any live cell with more than 3 live neighbors dies, as if by overpopulation.
if (map[i][j] && neighbors > 3) {
nextMap[i][j] = false;
}
// 4. Any dead cell with exactly 3 live neighbors becomes a live cell, as if by reproduction.
if (!map[i][j] && neighbors == 3) {
nextMap[i][j] = true;
}
}
}
map = nextMap;
// Clear console
ClearCLI();
PrintHeader();
PrintMap(map, false, false);
System.out.printf("Generation: %d/%d", generations, maxGens);
Wait(refreshRate);
generations++;
}
return map;
}
public static void ClearCLI() {
try {
new ProcessBuilder("cmd", "/c", "cls").inheritIO().start().waitFor();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
public static void Wait(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void PrintHeader() {
System.out.printf("%s%n%s%n%s%n",
"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~",
"Conway's Game Of Life",
"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
}
public static void PrintMap(boolean[][] map, boolean brackets, boolean headers) {
String cell = brackets ? "[■ ]" : " ■ ";
String noCell = brackets ? "[ ]" : " ";
int r = map.length;
int c = map[0].length;
String header = "";
// Column header print
for (int i = 0; i < c; i++) {
// Top left corner is blank
if (i == 0) {
System.out.print(" ");
}
header = headers ? String.format("[%2d]", i) : " ";
System.out.print(header);
}
System.out.printf("%n");
// Map print
for (int i = 0; i < r; i++) {
// Row header print
header = headers ? String.format("[%2d]", i) : " ";
System.out.print(header);
for (int j = 0; j < c; j++) {
System.out.printf("%s", map[i][j] ? cell : noCell);
}
System.out.printf("%n");
}
}
public static void PrintMessage(String message) {
System.out.printf("%n%s%n", message);
}
public static void Bye() {
System.out.printf("%s%n", "Bye.");
}
public static void SaveMap(String fileName, boolean[][] map) throws IOException {
Path path = Path.of(fileName);
int i = 0;
StringBuilder str = new StringBuilder();
for (boolean[] row : map) {
for (boolean cell : row) {
str.append(cell ? "[x]" : "[ ]");
}
// Windows newline sequence is CR LF (Carriage Return Line Feed)
// If this was the last row, don't set a newline.
// this isn't very professional is it
if (i < map.length - 1) {
// "\r\n" = "1310"
str.append("\r\n");
}
i++;
}
byte[] chars = str.toString().getBytes();
Files.write(path, chars);
}
public static boolean[][] LoadMap(String filename) throws IOException {
Path path = Path.of(filename);
byte[] contents = Files.readAllBytes(path);
String str = "";
//Count rows and columns
int r = 0, c = 0;
//There is always one row at least.
r++;
boolean cCounting = true;
for (byte ch : contents) {
str += ch;
// if [ ] or [x]
// Clear string. Add column count if still on the first row.
if (str.equals("913293") || str.equals("9112093")) {
str = "";
// If we are still counting columns.
if (cCounting) {
c++;
}
continue;
}
// if newline
// Clear string. Stop counting columns.
// "\r\n" = "1310"
if (str.equals("1310")) {
// Stop counting columns when new line detected.
cCounting = false;
str = "";
// If new line, then new row!
r++;
}
}
//Don't proceed if attempting to load an empty map.
if (r == 1 || c == 0) {
throw new Error();
}
boolean[][] map = new boolean[r][c];
int i = 0;
int j = 0;
for (byte ch : contents) {
str += ch;
// if [ ]
if (str.equals("913293")) {
map[i][j] = false;
str = "";
j++;
}
// if [x]
if (str.equals("9112093")) {
map[i][j] = true;
str = "";
j++;
}
// if newline
// "\r\n" = "1310"
if (str.equals("1310")) {
str = "";
i++;
j = 0;
}
}
return map;
}
public static void Dir(String dir) {
/*
Get dir contents
*/
}
}
Go back