import java.util.*;
import java.util.stream.IntStream;
/**
* Life for Two
*
* The original BASIC program uses a grid with an extras border of cells all around,
* probably to simplify calculations and manipulations. This java program has the exact
* grid size and instead uses boundary check conditions in the logic.
*
* Converted from BASIC to Java by Aldrin Misquitta (@aldrinm)
*/
public class LifeForTwo {
final static int GRID_SIZE = 5;
//Pair of offset which when added to the current cell's coordinates,
// give the coordinates of the neighbours
final static int[] neighbourCellOffsets = {
-1, 0,
1, 0,
0, -1,
0, 1,
-1, -1,
1, -1,
-1, 1,
1, 1
};
//The best term that I could come with to describe these numbers was 'masks'
//They act like indicators to decide which player won the cell. The value is the score of the cell after all the
// generation calculations.
final static List maskPlayer1 = List.of(3, 102, 103, 120, 130, 121, 112, 111, 12);
final static List maskPlayer2 = List.of(21, 30, 1020, 1030, 1011, 1021, 1003, 1002, 1012);
public static void main(String[] args) {
printIntro();
Scanner scan = new Scanner(System.in);
scan.useDelimiter("\\D");
int[][] grid = new int[GRID_SIZE][GRID_SIZE];
initializeGrid(grid);
//Read the initial 3 moves for each player
for (int b = 1; b <= 2; b++) {
System.out.printf("\nPLAYER %d - 3 LIVE PIECES.%n", b);
for (int k1 = 1; k1 <= 3; k1++) {
var player1Coordinates = readUntilValidCoordinates(scan, grid);
grid[player1Coordinates.x - 1][player1Coordinates.y - 1] = (b == 1 ? 3 : 30);
}
}
printGrid(grid);
calculatePlayersScore(grid); //Convert 3, 30 to 100, 1000
resetGridForNextGen(grid);
computeCellScoresForOneGen(grid);
var playerScores = calculatePlayersScore(grid);
resetGridForNextGen(grid);
boolean gameOver = false;
while (!gameOver) {
printGrid(grid);
if (playerScores.getPlayer1Score() == 0 && playerScores.getPlayer2Score() == 0) {
System.out.println("\nA DRAW");
gameOver = true;
} else if (playerScores.getPlayer2Score() == 0) {
System.out.println("\nPLAYER 1 IS THE WINNER");
gameOver = true;
} else if (playerScores.getPlayer1Score() == 0) {
System.out.println("\nPLAYER 2 IS THE WINNER");
gameOver = true;
} else {
System.out.print("PLAYER 1 ");
Coordinate player1Move = readCoordinate(scan);
System.out.print("PLAYER 2 ");
Coordinate player2Move = readCoordinate(scan);
if (!player1Move.equals(player2Move)) {
grid[player1Move.x - 1][player1Move.y - 1] = 100;
grid[player2Move.x - 1][player2Move.y - 1] = 1000;
}
//In the original, B is assigned 99 when both players choose the same cell
//and that is used to control the flow
computeCellScoresForOneGen(grid);
playerScores = calculatePlayersScore(grid);
resetGridForNextGen(grid);
}
}
}
private static void initializeGrid(int[][] grid) {
for (int[] row : grid) {
Arrays.fill(row, 0);
}
}
private static void computeCellScoresForOneGen(int[][] grid) {
for (int i = 0; i < GRID_SIZE; i++) {
for (int j = 0; j < GRID_SIZE; j++) {
if (grid[i][j] >= 100) {
calculateScoreForOccupiedCell(grid, i, j);
}
}
}
}
private static Scores calculatePlayersScore(int[][] grid) {
int m2 = 0;
int m3 = 0;
for (int i = 0; i < GRID_SIZE; i++) {
for (int j = 0; j < GRID_SIZE; j++) {
if (grid[i][j] < 3) {
grid[i][j] = 0;
} else {
if (maskPlayer1.contains(grid[i][j])) {
m2++;
} else if (maskPlayer2.contains(grid[i][j])) {
m3++;
}
}
}
}
return new Scores(m2, m3);
}
private static void resetGridForNextGen(int[][] grid) {
for (int i = 0; i < GRID_SIZE; i++) {
for (int j = 0; j < GRID_SIZE; j++) {
if (grid[i][j] < 3) {
grid[i][j] = 0;
} else {
if (maskPlayer1.contains(grid[i][j])) {
grid[i][j] = 100;
} else if (maskPlayer2.contains(grid[i][j])) {
grid[i][j] = 1000;
} else {
grid[i][j] = 0;
}
}
}
}
}
private static void calculateScoreForOccupiedCell(int[][] grid, int i, int j) {
var b = 1;
if (grid[i][j] > 999) {
b = 10;
}
for (int k = 0; k < 15; k += 2) {
//check bounds
var neighbourX = i + neighbourCellOffsets[k];
var neighbourY = j + neighbourCellOffsets[k + 1];
if (neighbourX >= 0 && neighbourX < GRID_SIZE &&
neighbourY >= 0 && neighbourY < GRID_SIZE) {
grid[neighbourX][neighbourY] = grid[neighbourX][neighbourY] + b;
}
}
}
private static void printGrid(int[][] grid) {
System.out.println();
printRowEdge();
System.out.println();
for (int i = 0; i < grid.length; i++) {
System.out.printf("%d ", i + 1);
for (int j = 0; j < grid[i].length; j++) {
System.out.printf(" %c ", mapChar(grid[i][j]));
}
System.out.printf(" %d", i + 1);
System.out.println();
}
printRowEdge();
System.out.println();
}
private static void printRowEdge() {
System.out.print("0 ");
IntStream.range(1, GRID_SIZE + 1).forEach(i -> System.out.printf(" %s ", i));
System.out.print(" 0");
}
private static char mapChar(int i) {
if (i == 3 || i == 100) {
return '*';
}
if (i == 30 || i == 1000) {
return '#';
}
return ' ';
}
private static Coordinate readUntilValidCoordinates(Scanner scanner, int[][] grid) {
boolean coordinateInRange = false;
Coordinate coordinate = null;
while (!coordinateInRange) {
coordinate = readCoordinate(scanner);
if (coordinate.x <= 0 || coordinate.x > GRID_SIZE
|| coordinate.y <= 0 || coordinate.y > GRID_SIZE
|| grid[coordinate.x - 1][coordinate.y - 1] != 0) {
System.out.println("ILLEGAL COORDS. RETYPE");
} else {
coordinateInRange = true;
}
}
return coordinate;
}
private static Coordinate readCoordinate(Scanner scanner) {
Coordinate coordinate = null;
int x, y;
boolean valid = false;
System.out.println("X,Y");
System.out.print("XXXXXX\r");
System.out.print("$$$$$$\r");
System.out.print("&&&&&&\r");
while (!valid) {
try {
System.out.print("? ");
y = scanner.nextInt();
x = scanner.nextInt();
valid = true;
coordinate = new Coordinate(x, y);
} catch (InputMismatchException e) {
System.out.println("!NUMBER EXPECTED - RETRY INPUT LINE");
valid = false;
} finally {
scanner.nextLine();
}
}
return coordinate;
}
private static void printIntro() {
System.out.println(" LIFE2");
System.out.println(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
System.out.println("\n\n");
System.out.println("\tU.B. LIFE GAME");
}
private static class Coordinate {
private final int x, y;
public Coordinate(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
@Override
public String toString() {
return "Coordinate{" +
"x=" + x +
", y=" + y +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Coordinate that = (Coordinate) o;
return x == that.x && y == that.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}
private static class Scores {
private final int player1Score;
private final int player2Score;
public Scores(int player1Score, int player2Score) {
this.player1Score = player1Score;
this.player2Score = player2Score;
}
public int getPlayer1Score() {
return player1Score;
}
public int getPlayer2Score() {
return player2Score;
}
}
}