From 7b97b7861dca010772a266502dea578dcb8ae3ab Mon Sep 17 00:00:00 2001 From: natha Date: Fri, 31 May 2024 21:12:20 +0200 Subject: [PATCH] final version in the right gitarero folder this time --- src/backend/Cell.java | 45 ++++++ src/backend/GameScreen.java | 160 ++++++++++++++++++++ src/backend/Grid.java | 41 ++++++ src/backend/Sheep.java | 55 +++++-- src/backend/Simulator.java | 285 +++++++++++++++++++++++++----------- src/backend/Wolf.java | 83 +++++++++++ 6 files changed, 574 insertions(+), 95 deletions(-) create mode 100644 src/backend/Cell.java create mode 100644 src/backend/GameScreen.java create mode 100644 src/backend/Grid.java create mode 100644 src/backend/Wolf.java diff --git a/src/backend/Cell.java b/src/backend/Cell.java new file mode 100644 index 0000000..6e5f574 --- /dev/null +++ b/src/backend/Cell.java @@ -0,0 +1,45 @@ +package backend; + +public class Cell { + int positionX; // X position + int positionY; // Y position + int state; // Current state of the cell + int timeOfLifeSinceCut = 0; // Time since the cell was last alive + int GROWING_TIME = 50; // Time it takes to grow back + + // Constructor to initialize the cell + public Cell(int x, int y, int lifeState) { + positionX = x; + positionY = y; + state = lifeState; + } + + // Change the cell's state + public void toggleCell() { + state++; + if (state > 5) { + state = 0; + } + } + + // Set the cell's state + public void setState(int lifeState) { + state = lifeState; + } + + // Update the cell's state based on time + public void updateCell() { + if (state == 0) { // If the cell is dead + timeOfLifeSinceCut++; + if (timeOfLifeSinceCut > GROWING_TIME) { // If enough time has passed + timeOfLifeSinceCut = 0; + state = 1; // Grow the cell back + } + } + } + + // Get the cell's state + public int getState() { + return state; + } +} diff --git a/src/backend/GameScreen.java b/src/backend/GameScreen.java new file mode 100644 index 0000000..2f3944b --- /dev/null +++ b/src/backend/GameScreen.java @@ -0,0 +1,160 @@ +package backend; + +import java.util.ArrayList; +import java.util.Random; +import java.util.stream.IntStream; + +public class GameScreen { + + private Grid grid; // The grid representing the game world + private int width; // Width of the game screen + private int height; // Height of the game screen + private boolean loopingBorder; // Flag to determine if borders are looping + private ArrayList> rule; // Rules for cell evolution + + // Constructor to initialize the game screen with a given size and rules + public GameScreen(int sizeX, int sizeY, ArrayList> _rule) { + width = sizeX; + height = sizeY; + rule = _rule; + grid = new Grid(sizeX, sizeY); // Initialize the grid with the given size + } + + // Method to update the state of a specific cell + public void updateCell(int x, int y) { + grid.getCell(x, y).updateCell(); // Update the cell at (x, y) + } + + // Method to check the number of neighbors of a cell that are in a specific state + private int checkNeighbors(int x, int y, int state) { + int numberNeighbors = 0; // Counter for neighbors + int width = getWidth(); // Get the width of the grid + int height = getHeight(); // Get the height of the grid + + // Iterate over the 3x3 neighborhood around the cell at (x, y) + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + if (i == 0 && j == 0) continue; // Skip the cell itself + + int NeighborX = x + i; // Calculate the x coordinate of the neighbor + int NeighborY = y + j; // Calculate the y coordinate of the neighbor + + // Handle looping borders if enabled + if (loopingBorder) { + NeighborX = (NeighborX + width) % width; + NeighborY = (NeighborY + height) % height; + } + + // Check if the neighbor cell is within valid bounds + if (isValidCell(NeighborX, NeighborY, width, height)) { + // Increment the neighbor counter if the neighbor cell is in the desired state + if (grid.getCell(NeighborX, NeighborY).getState() == state) { + numberNeighbors++; + } + } + } + } + + System.out.println(numberNeighbors); // Print the number of neighbors (for debugging) + return numberNeighbors; // Return the number of neighbors + } + + // Method to check if a cell coordinate is within the grid bounds + private boolean isValidCell(int x, int y, int width, int height) { + return x >= 0 && x < width && y >= 0 && y < height; + } + + // Method to advance the game by one step + public void makeStep() { + int width = getWidth(); // Get the width of the grid + int height = getHeight(); // Get the height of the grid + Grid nextGrid = new Grid(width, height); // Create a new grid for the next state + + // Iterate over all cells in the grid + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int presentState = getCell(x, y); // Get the current state of the cell + int futureState = determineFutureState(x, y, presentState); // Determine the future state of the cell + nextGrid.setCell(x, y, futureState); // Set the future state in the new grid + } + } + grid = nextGrid; // Update the grid to the new state + } + + // Method to determine the future state of a cell based on its current state + private int determineFutureState(int x, int y, int presentState) { + if (presentState == 0) { + return determineStateForDeadCell(x, y); // Determine state for a dead cell + } else { + return determineStateForLiveCell(x, y, presentState); // Determine state for a live cell + } + } + + // Method to determine the future state of a dead cell based on neighbor states + private int determineStateForDeadCell(int x, int y) { + for (int i = 1; i <= 5; i++) { + if (rule.get(0).contains(checkNeighbors(x, y, i))) { + return i; // Return the state if the rule matches the neighbor count + } + } + return 0; // Remain dead if no rules match + } + + // Method to determine the future state of a live cell based on neighbor states + private int determineStateForLiveCell(int x, int y, int presentState) { + if (rule.get(1).contains(checkNeighbors(x, y, presentState))) { + return presentState; // Remain in the same state if the rule matches the neighbor count + } + return 0; // Die if no rules match + } + + // Method to randomly generate the initial state of the grid + public void generateRandom(float chanceOfLife) { + int width = getWidth(); // Get the width of the grid + int height = getHeight(); // Get the height of the grid + Random random = new Random(); // Create a random number generator + + // Iterate over all cells in the grid + IntStream.range(0, height).forEach(y -> + IntStream.range(0, width).forEach(x -> + // Set the cell state based on the chance of life + setCell(x, y, random.nextFloat() < chanceOfLife ? 1 : 0) + ) + ); + } + + // Method to toggle the state of a cell when clicked + public void clickCell(int x, int y) { + grid.getCell(x, y).toggleCell(); // Toggle the cell state at (x, y) + } + + // Method to set the state of a specific cell + public void setCell(int x, int y, int val) { + grid.getCell(x, y).setState(val); // Set the state of the cell at (x, y) + } + + // Method to set the rules for cell evolution + public void setRules(ArrayList> _rule) { + rule = _rule; // Update the rules + } + + // Method to enable or disable looping borders + public void setLoopingBorders(boolean loop) { + loopingBorder = loop; // Set the looping border flag + } + + // Method to get the state of a specific cell + public int getCell(int x, int y) { + return grid.getCell(x, y).getState(); // Return the state of the cell at (x, y) + } + + // Method to get the width of the grid + public int getWidth() { + return grid.getWidth(); // Return the width of the grid + } + + // Method to get the height of the grid + public int getHeight() { + return grid.getHeight(); // Return the height of the grid + } +} diff --git a/src/backend/Grid.java b/src/backend/Grid.java new file mode 100644 index 0000000..971b016 --- /dev/null +++ b/src/backend/Grid.java @@ -0,0 +1,41 @@ +package backend; + +public class Grid { + private Cell[][] playTable; // The grid of cells + private int width; // Grid width + private int height; // Grid height + + // Constructor to initialize the grid + public Grid(int X, int Y) { + width = X; + height = Y; + playTable = new Cell[width][height]; + + // Initialize all cells in the grid + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + playTable[x][y] = new Cell(x, y, 0); // Set initial state to 0 + } + } + } + + // Get a cell from the grid + public Cell getCell(int x, int y) { + return playTable[x][y]; + } + + // Set the state of a specific cell + public void setCell(int x, int y, int value) { + playTable[x][y].setState(value); + } + + // Get the grid width + public int getWidth() { + return width; + } + + // Get the grid height + public int getHeight() { + return height; + } +} diff --git a/src/backend/Sheep.java b/src/backend/Sheep.java index ece8a18..766bb6e 100644 --- a/src/backend/Sheep.java +++ b/src/backend/Sheep.java @@ -10,9 +10,14 @@ import java.util.Random; // for example wolves that eat Sheep public class Sheep extends Agent { - int hunger; - Random rand; - + int hunger; // Hunger level + Random rand; // Random number generator + int height; // World height + int width; // World width + private final int REPRODUCTION_RADIUS = 1; // Radius within which sheep can reproduce + private final double REPRODUCTION_PROBABILITY = 0.05; // Probability of reproduction per turn + + // Constructor to initialize the sheep Sheep(int x,int y){ //first we call the constructor of the superClass(Animal) //with the values we want. @@ -31,29 +36,57 @@ public class Sheep extends Agent { */ public boolean liveTurn(ArrayList neighbors, Simulator world) { if(world.getCell(x, y)==1) { - world.setCell(x, y, 0); + world.setCell(x, y, 0);// Eat if on food } else { - hunger++; + hunger++;// Increase hunger if no food } + height = world.getHeight(); + width = world.getHeight(); + + // Check for reproduction + for (Agent neighbor : neighbors) { + if (neighbor instanceof Sheep && this.isInArea(neighbor.getX(), neighbor.getY(), REPRODUCTION_RADIUS)) { + if (rand.nextDouble() < REPRODUCTION_PROBABILITY) { + reproduce(world); + break; + } + } + } this.moveRandom(); - return hunger>10; + return hunger>10;// Return true if the sheep is hungry over the limit } + + private void reproduce(Simulator world) { + // Find a random adjacent cell + int newX = x + rand.nextInt(3) - 1; + int newY = y + rand.nextInt(3) - 1; + // Ensure the new position is within bounds and empty + if (newX >= 0 && newX < world.getWidth() && newY >= 0 && newY < world.getHeight() && !world.getOccupied(newX, newY)) { + world.setAgent(newX, newY, new Sheep(newX, newY)); + } + } + // Move randomly private void moveRandom() { int direction = rand.nextInt(4); + int nx = x; + int ny = y; + if(direction == 0) { - x+=1; + nx+=1; } if(direction == 1) { - y+=1; + ny+=1; } if(direction == 2) { - x-=1; + nx-=1; } if(direction == 3) { - y-=1; + ny-=1; } + + } } -} + diff --git a/src/backend/Simulator.java b/src/backend/Simulator.java index 9774cd3..0625359 100644 --- a/src/backend/Simulator.java +++ b/src/backend/Simulator.java @@ -1,5 +1,6 @@ package backend; import java.util.ArrayList; +import java.util.Iterator; import windowInterface.MyInterface; @@ -13,17 +14,19 @@ public class Simulator extends Thread { //Conway Radius : 1 private final int LIFE_AREA_RADIUS = 1; //Animal Neighborhood Radius : 5 - private final int ANIMAL_AREA_RADIUS = 2; - private ArrayList fieldSurviveValues; - private ArrayList fieldBirthValues; + private final int ANIMAL_AREA_RADIUS = 3; + private ArrayList surviveValues; + private ArrayList birthValues; private ArrayList agents; - + ArrayList> rule = new ArrayList<>(); + private boolean stopFlag; private boolean pauseFlag; private boolean loopingBorder; private boolean clickActionFlag; private int loopDelay = 150; + private GameScreen gameScreen; //TODO : add missing attribute(s) @@ -35,28 +38,28 @@ public class Simulator extends Thread { clickActionFlag=false; agents = new ArrayList(); - fieldBirthValues = new ArrayList(); - fieldSurviveValues = new ArrayList(); + birthValues = new ArrayList(); + surviveValues = new ArrayList(); + + + // Normal Game of Life rules + birthValues.add(3); // Birth condition + surviveValues.add(2); // Survival condition + surviveValues.add(3); // Survival condition - //TODO : add missing attribute initialization - - - - //Default rule : Survive always, birth never - for(int i =0; i<9; i++) { - fieldSurviveValues.add(i); - } + rule.add(birthValues); + rule.add(surviveValues); + gameScreen = new GameScreen(COL_NUM, LINE_NUM, rule); } public int getWidth() { - //TODO : replace with proper return - return 0; + return gameScreen.getWidth(); } public int getHeight() { //TODO : replace with proper return - return 0; + return gameScreen.getHeight(); } //Should probably stay as is @@ -91,17 +94,15 @@ public class Simulator extends Thread { // only modify if sure of what you do // to modify agent behavior, see liveTurn method // in agent classes - for(Agent agent : agents) { - ArrayList neighbors = - this.getNeighboringAnimals( - agent.getX(), - agent.getY(), - ANIMAL_AREA_RADIUS); - if(!agent.liveTurn( - neighbors, - this)) { - agents.remove(agent); - } + //Modified because of cross-modifications to the arraylist and java doesn't like that much + for (int i = 0; i < agents.size(); i++) { + Agent agent = agents.get(i); + ArrayList neighbors = this.getNeighboringAnimals(agent.getX(), agent.getY(), ANIMAL_AREA_RADIUS); + + if (agent.liveTurn(neighbors, this)) { + agents.remove(i); + i--; // Adjust the index to account for the removed element + } } //then evolution of the field // TODO : apply game rule to all cells of the field @@ -119,10 +120,9 @@ public class Simulator extends Thread { * and the count is in the birth list, * then the cell becomes alive */ - - - - + if (!clickActionFlag) + gameScreen.makeStep(); + else updateCells(); } /* @@ -136,35 +136,99 @@ public class Simulator extends Thread { * method called when clicking pause button */ public void togglePause() { - // TODO : actually toggle the corresponding flag + pauseFlag = !pauseFlag; } /** * method called when clicking on a cell in the interface */ public void clickCell(int x, int y) { - //TODO : complete method + if (clickActionFlag) { + setAgent(x, y); + } else { + gameScreen.clickCell(x, y);; + } + } + + public void setAgent(int x, int y) { + int cellValue = getCell(x, y); + String agentType = null; + int indexOfAgent = 0; + + for (int i = 0; i < agents.size(); i++) { + Agent agent = agents.get(i); + if (agent.getX() == x && agent.getY() == y) { + if (agent instanceof Sheep) { + agentType = "Sheep"; + } else { + agentType = "Wolf"; + } + indexOfAgent = i; + break; + } + } + + if (cellValue == 0 && agentType == null) { + setCell(x, y, 1); + } else if (cellValue == 1) { + agents.add(new Sheep(x, y)); + setCell(x, y, 0); + } else if ("Sheep".equals(agentType)) { + agents.remove(indexOfAgent); + agents.add(new Wolf(x, y)); + } else if ("Wolf".equals(agentType)) { + agents.remove(indexOfAgent); + setCell(x, y, 0); + } + } + + + public void setAgent(int x, int y, Agent agent) { + agents.add(agent); + setCell(x, y, 0); // Clear the cell if it had food + } + + + public void removeAgent(Agent agent) { + agents.remove(agent); } /** - * get cell value in simulated world + * get cell value in simulated gameScreen * @param x coordinate of cell * @param y coordinate of cell * @return value of cell */ public int getCell(int x, int y) { - //TODO : complete method with proper return - return 0; + return gameScreen.getCell(x, y); } + + private void updateCells() { + for (int y = 0; y < getHeight(); y++) { + for (int x = 0; x < getWidth(); x++) { + gameScreen.updateCell(x, y); + } + } + } + /** * - * @return list of Animals in simulated world + * @return list of Animals in simulated gameScreen */ public ArrayList getAnimals(){ return agents; } + public boolean getOccupied(int x, int y) { + for (Agent agent : agents) { + if (agent.getX() == x && agent.getY() == y) { + return true; + } + } + return false; + } + /** - * selects Animals in a circular area of simulated world + * selects Animals in a circular area of simulated gameScreen * @param x center * @param y center * @param radius @@ -180,7 +244,7 @@ public class Simulator extends Thread { } return inArea; } - + /** * set value of cell * @param x coord of cell @@ -188,21 +252,35 @@ public class Simulator extends Thread { * @param val to set in cell */ public void setCell(int x, int y, int val) { - //TODO : complete method + gameScreen.setCell(x, y, val); } /** * * @return lines of file representing - * the simulated world in its present state + * the simulated gameScreen in its present state */ public ArrayList getSaveState() { - //TODO : complete method with proper return - return null; + ArrayList lines = new ArrayList<>(); + + // Iterate over the gameScreen and construct the lines + for (int y = 0; y < LINE_NUM; y++) { + StringBuilder lineBuilder = new StringBuilder(); + for (int x = 0; x < COL_NUM; x++) { + lineBuilder.append(getCell(x, y)).append(";"); + } + // Remove the last semicolon + if (lineBuilder.length() > 0) { + lineBuilder.deleteCharAt(lineBuilder.length() - 1); + } + lines.add(lineBuilder.toString()); + } + + return lines; } /** * - * @param lines of file representing saved world state + * @param lines of file representing saved gameScreen state */ public void loadSaveState(ArrayList lines) { /* @@ -220,7 +298,7 @@ public class Simulator extends Thread { return; } /* - * now we fill in the world + * now we fill in the gameScreen * with the content of the file */ for(int y =0; y getRule() { - //TODO : complete method with proper return - - return null; + ArrayList lines = new ArrayList<>(); + + // Add birth conditions + for (Integer birthCondition : rule.get(0)) { + lines.add(birthCondition.toString()); + } + + // Add survival conditions + for (Integer survivalCondition : rule.get(1)) { + lines.add(survivalCondition.toString()); + } + + return lines; } public void loadRule(ArrayList lines) { @@ -288,8 +360,9 @@ public class Simulator extends Thread { System.out.println("empty rule file"); return; } - //TODO : remove previous rule (=emptying lists) - + rule.clear(); + birthValues.clear(); + surviveValues.clear(); String surviveLine = lines.get(0); String birthLine = lines.get(1); @@ -297,36 +370,80 @@ public class Simulator extends Thread { for(int x=0; x getAgentsSave() { - //TODO : Same idea as the other save method, but for agents - return null; + ArrayList lines = new ArrayList<>(); + + StringBuilder sheepCoordinates = new StringBuilder(); + StringBuilder wolfCoordinates = new StringBuilder(); + + for (Agent agent : agents) { + String coordinates = agent.getX() + "," + agent.getY(); + if (agent instanceof Sheep) { + if (sheepCoordinates.length() > 0) { + sheepCoordinates.append(";"); + } + sheepCoordinates.append(coordinates); + } else if (agent instanceof Wolf) { + if (wolfCoordinates.length() > 0) { + wolfCoordinates.append(";"); + } + wolfCoordinates.append(coordinates); + } + } + + lines.add(sheepCoordinates.toString()); + lines.add(wolfCoordinates.toString()); + + return lines; } public void loadAgents(ArrayList stringArray) { - //TODO : Same idea as other load methods, but for agent list - + agents.clear(); + + // Load sheeps + String sheepLine = stringArray.get(0); + String[] sheepCoords = sheepLine.split(";"); + for (String coord : sheepCoords) { + String[] xy = coord.split(","); + int x = Integer.parseInt(xy[0]); + int y = Integer.parseInt(xy[1]); + agents.add(new Sheep(x, y)); + } + + // Load wolves + String wolfLine = stringArray.get(1); + String[] wolfCoords = wolfLine.split(";"); + for (String coord : wolfCoords) { + String[] xy = coord.split(","); + int x = Integer.parseInt(xy[0]); + int y = Integer.parseInt(xy[1]); + agents.add(new Wolf(x, y)); + } } + + /** * used by label in interface to show the active click action * @return String representation of click action */ public String clickActionName() { - // TODO : initially return "sheep" or "cell" - // depending on clickActionFlag - return ""; + return clickActionFlag ? "Agents" : "Cells"; } } diff --git a/src/backend/Wolf.java b/src/backend/Wolf.java new file mode 100644 index 0000000..8f5487f --- /dev/null +++ b/src/backend/Wolf.java @@ -0,0 +1,83 @@ +package backend; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.Random; + +public class Wolf extends Agent { + + int hunger; + Random rand; + private final int REPRODUCTION_RADIUS = 1; // radius max of reproduction + private final double REPRODUCTION_PROBABILITY = 0.2; // proba of reproduction + + Wolf(int x, int y) { + super(x, y, Color.red); + hunger = 0; + rand = new Random(); // generate random + } + + /** + * The main behavior of the wolf each turn. + * @param neighbors List of neighboring agents + * @param world The simulation world + * @return true if the wolf is starving and should be removed + */ + @Override + public boolean liveTurn(ArrayList neighbors, Simulator world) { + boolean ateSheep = false; + + // check to see if can eat a sheep + for (Agent neighbor : neighbors) { + if (neighbor instanceof Sheep) { + world.removeAgent(neighbor); // eat the sheep so removes it + hunger = 0; // rest hunger to 0 + ateSheep = true; + break; + } + } + + // if not eating increase hunger + if (!ateSheep) { + hunger++; + } + + // Check for reproduction with neighboring wolves + for (Agent neighbor : neighbors) { + if (neighbor instanceof Wolf && isInArea(neighbor.getX(), neighbor.getY(), REPRODUCTION_RADIUS)) { + if (rand.nextDouble() < REPRODUCTION_PROBABILITY) { + reproduce(world); // Reproduce a new wolf + break; + } + } + } + + // Move randomly + moveRandom(); + + + return hunger > 10; //cehck if the wolf is hungry above the limit + } + + //reproduction makes a wolve appear on a adjacent cell + private void reproduce(Simulator world) { + int newX = x + rand.nextInt(3) - 1; + int newY = y + rand.nextInt(3) - 1; + + // Ensure the new position is within bounds and empty + if (newX >= 0 && newX < world.getWidth() && newY >= 0 && newY < world.getHeight() && world.getCell(newX, newY) == 0) { + world.setAgent(newX, newY, new Wolf(newX, newY)); + } + } + + // Move randomly + private void moveRandom() { + int direction = rand.nextInt(4); + switch (direction) { + case 0 : x += 1; + case 1 : y += 1; + case 2 : x -= 1; + case 3 : y -= 1; + } + } +}