diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..6c99f0a Binary files /dev/null and b/.DS_Store differ diff --git a/src/backend/Agent.java b/src/backend/Agent.java index 8b11e23..f5c428f 100644 --- a/src/backend/Agent.java +++ b/src/backend/Agent.java @@ -13,16 +13,20 @@ public abstract class Agent { this.y = y; this.color = color; } + public Color getDisplayColor() { return color; } + public int getX() { return x; } + public int getY() { return y; } + public boolean isInArea(int x, int y, int radius) { int diffX = this.x-x; int diffY = this.y-y; @@ -30,11 +34,23 @@ public abstract class Agent { return dist neighbors, Simulator world); + public abstract String type(); + } diff --git a/src/backend/CellField.java b/src/backend/CellField.java new file mode 100644 index 0000000..77f884b --- /dev/null +++ b/src/backend/CellField.java @@ -0,0 +1,41 @@ +package backend; +import java.util.Random; +//The CellField class represents a grid of cells, used to simulate environments +//Each cell in the grid can hold an integer value, used to represent different states +public class CellField { + private int[][] grid; + + public CellField(int width, int height) { + grid = new int[width][height]; + } + + public void setCell(int x, int y, int val) { + if (x >= 0 && x < grid.length && y >= 0 && y < grid[0].length) { + grid[x][y] = val; + } else { + System.out.println("Invalid coordinates: (" + x + ", " + y + ")"); + } + } + + public void generateRandom(float chanceOfLife) { + Random random = new Random(); + for (int x = 0; x < grid.length; x++) { + for (int y = 0; y < grid[0].length; y++) { + if (random.nextFloat() < chanceOfLife) { + setCell(x, y, 1); + } else { + setCell(x, y, 0); + } + } + } + } + + public int getCell(int x, int y) { + return grid[x][y]; + } + + public int[][] getGrid() { + return grid; + } + +} diff --git a/src/backend/Sheep.java b/src/backend/Sheep.java index ece8a18..ffe42df 100644 --- a/src/backend/Sheep.java +++ b/src/backend/Sheep.java @@ -1,19 +1,16 @@ package backend; import java.awt.Color; +import java.nio.file.AtomicMoveNotSupportedException; import java.util.ArrayList; import java.util.Random; -// example of basic animal. -// do not hesitate to make it more complex -// and DO add at least another species that interact with it -// for example wolves that eat Sheep public class Sheep extends Agent { int hunger; Random rand; - Sheep(int x,int y){ + public Sheep(int x,int y){ //first we call the constructor of the superClass(Animal) //with the values we want. // here we decide that a Sheep is initially white using this constructor @@ -30,30 +27,46 @@ public class Sheep extends Agent { * as you wish */ public boolean liveTurn(ArrayList neighbors, Simulator world) { + // Check if the current cell has food if(world.getCell(x, y)==1) { - world.setCell(x, y, 0); + world.setCell(x, y, 0);// Consume the food by setting the cell value to 0 + hunger --;//decrease hunger } else { hunger++; } - this.moveRandom(); - return hunger>10; + this.moveRandom(neighbors); + return hunger>10;// Return true if hunger is greater than 10, indicating the sheep is starving + } - private void moveRandom() { - int direction = rand.nextInt(4); - if(direction == 0) { + private void moveRandom(ArrayList neighbors) { + int direction = rand.nextInt(4); + if(direction == 0 && isAvailable(x+1 , y, neighbors)) { x+=1; } - if(direction == 1) { + if(direction == 1 && isAvailable(x , y+1, neighbors)) { y+=1; } - if(direction == 2) { + if(direction == 2 && isAvailable(x-1 , y, neighbors)) { x-=1; } - if(direction == 3) { + if(direction == 3 && isAvailable(x , y-1, neighbors)) { y-=1; } + } + + //check if no sheep in the next cell + private boolean isAvailable(int x, int y, ArrayList neighbors) { + for (Agent neighbor : neighbors) { + if(neighbor.getX() ==x && neighbor.getY() ==y) { + return false; + } + } + return true; + } + + public String type() { + return "sheep"; } - } diff --git a/src/backend/Simulator.java b/src/backend/Simulator.java index cdaf5ba..9fe541b 100644 --- a/src/backend/Simulator.java +++ b/src/backend/Simulator.java @@ -1,7 +1,14 @@ package backend; import java.util.ArrayList; -import java.util.Random; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import windowInterface.MyInterface; +import java.awt.Color; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; public class Simulator extends Thread { @@ -23,12 +30,15 @@ public class Simulator extends Thread { private boolean pauseFlag; private boolean loopingBorder; private boolean clickActionFlag; - private int loopDelay = 150; - private int[][] board; + private int loopDelay = 150; private int clickActionFlagValue; + private CellField cellField; + private ArrayList agentsToRemove; + private ArrayList agentsToAdd; + // Populate this list if new Agents added: + private final List> AGENT_LIST = Arrays.asList(Sheep.class,Wolf.class); - //TODO : add missing attribute(s) public Simulator(MyInterface mjfParam) { mjf = mjfParam; @@ -41,25 +51,23 @@ public class Simulator extends Thread { fieldBirthValues = new ArrayList(); fieldSurviveValues = new ArrayList(); - //TODO : add missing attribute initialization - board = new int [100][100]; - + cellField = new CellField(COL_NUM, LINE_NUM); //Default rule : Survive always, birth never for(int i =0; i<9; i++) { fieldSurviveValues.add(i); } + fieldBirthValues.add(3); + } public int getWidth() { - //TODO : replace with proper return return COL_NUM; } public int getHeight() { - //TODO : replace with proper return return LINE_NUM; } @@ -90,44 +98,73 @@ public class Simulator extends Thread { * method called at each step of the simulation * makes all the actions to go from one step to the other */ - public void makeStep() { - // agent behaviors first - // 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); - } - } - //then evolution of the field - // TODO : apply game rule to all cells of the field - - /* you should distribute this action in methods/classes - * don't write everything here ! - * - * the idea is first to get the surrounding values - * then count how many are alive - * then check if that number is in the lists of rules - * if the cell is alive - * and the count is in the survive list, - * then the cell stays alive - * if the cell is not alive - * and the count is in the birth list, - * then the cell becomes alive - */ - - - - - } + //iterates over each agent in the agents collection and performs a step of their lifecycle. If an agent dies during this step, it is removed from the collection + public void makeStep() { + // List to store agents to remove from the simulation + agentsToRemove = new ArrayList<>(); + // Iterate over each agent in the simulation + Iterator iterator = agents.iterator(); + while (iterator.hasNext()) { + Agent agent = iterator.next(); + // Get neighboring agents for the current agent + ArrayList neighbors = getNeighboringAnimals(agent.getX(), agent.getY(), ANIMAL_AREA_RADIUS); + // Check if the current agent survives this step, remove if not + if ( agent.liveTurn(neighbors, this)) { + iterator.remove(); + } + } + // Remove agents that died during this step + if (agentsToRemove.size()!=0) { + agents.removeAll(agentsToRemove); + } + // Calculate the next generation of cells in the cellular automaton + int[][] nextGeneration = new int[COL_NUM][LINE_NUM]; + + for (int x = 0; x < COL_NUM; x++) { + for (int y = 0; y < LINE_NUM; y++) { + int aliveNeighbors = countAliveNeighbors(x, y); + int currentState = cellField.getCell(x, y); + // Apply rules of the cellular automaton to determine cell's state in next generation + if (currentState == 1) { + if (fieldSurviveValues.contains(aliveNeighbors)) { + nextGeneration[x][y] = 1; + } else { + nextGeneration[x][y] = 0; + } + } else { + if (fieldBirthValues.contains(aliveNeighbors)) { + nextGeneration[x][y] = 1; + } else { + nextGeneration[x][y] = 0; + } + } + } + } + // Update the grid with the new generation of cells + for (int x = 0; x < COL_NUM; x++) { + System.arraycopy(nextGeneration[x], 0, cellField.getGrid()[x], 0, LINE_NUM); + } + } + + private int countAliveNeighbors(int x, int y) { + int count = 0; + + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + int neighborX = x + i; + int neighborY = y + j; + // We check if neighbor is within bounds and not the cell itself + if (neighborX >= 0 && neighborX < COL_NUM && neighborY >= 0 && neighborY < LINE_NUM && + !(i == 0 && j == 0)) { + count += cellField.getCell(neighborX, neighborY); + } + } + } + + return count; + } + + /* * leave this as is @@ -140,7 +177,6 @@ public class Simulator extends Thread { * method called when clicking pause button */ public void togglePause() { - // TODO : actually toggle the corresponding flag pauseFlag = !pauseFlag; // Toggles the value of pauseFlag } @@ -148,29 +184,58 @@ public class Simulator extends Thread { * method called when clicking on a cell in the interface */ - // Method to handle different behaviors based on the clickActionFlagValue + //handles user interaction with the grid, allowing for the addition, replacement, or removal of agents based on user clicks or cell states public void clickCell(int x, int y) { - //TODO : complete method - board[x][y] = (board[x][y] == 0) ? 1 : 0; - } + // Check if a specific action flag is set + if (clickActionFlagValue != 0) { + // Check if an agent already exists at the clicked cell + Agent agentExists = null; + for (Agent agent : agents) { + if (agent.getX() == x && agent.getY() == y) { + agentExists = agent; + break; + } + } + // Get the class of the agent to be added or replaced + if (agentExists != null) { + Class agentClass = AGENT_LIST.get(clickActionFlagValue - 1); + try { + // Create a new instance of the agent class at the clicked cell + Constructor constructor = agentClass.getConstructor(int.class, int.class); + Agent agent = (Agent) constructor.newInstance(x, y); + + // If the new agent type matches the existing agent type, replace it + if(agent.type() == agentExists.type()) { + agents.remove(agents.indexOf(agentExists)); + } else { + agents.remove(agents.indexOf(agentExists)); + agents.add(agent); + } + } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { + e.printStackTrace(); + } + } else { + // If no agent exists at the clicked cell, add a new agent + Class agentClass = AGENT_LIST.get(clickActionFlagValue - 1); + try { + Constructor constructor = agentClass.getConstructor(int.class, int.class); + Agent agent = (Agent) constructor.newInstance(x, y); + agents.add(agent); + } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { + e.printStackTrace(); + } + + } + } else { + // If no specific action flag is set, toggle the state of the cell + cellField.setCell(x, y, (cellField.getCell(x, y) == 0) ? 1 : 0); + } + } - - /** - * get cell value in simulated world - * @param x coordinate of cell - * @param y coordinate of cell - * @return value of cell - */ // Method to get the value of cell at coordinates x, y public int getCell(int x, int y) { - //TODO : complete method with proper return - if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) { - return board[x][y]; - } else { - // Handle out of bounds condition or return default value - return -1; // We can choose any default value here - } + return cellField.getCell(x, y); } /** @@ -205,14 +270,7 @@ public class Simulator extends Thread { * @param val to set in cell */ public void setCell(int x, int y, int val) { - //TODO : complete method - if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) { - board[x][y] = val; - } else { - // Handle out of bounds condition or throw an exception - System.out.println("Invalid coordinates: (" + x + ", " + y + ")"); - - } + cellField.setCell(x, y, val); } /** @@ -221,9 +279,19 @@ public class Simulator extends Thread { * the simulated world in its present state */ public ArrayList getSaveState() { - //TODO : complete method with proper return - return null; - } + ArrayList saveState = new ArrayList<>(); + for (int y = 0; y < LINE_NUM; y++) { + StringBuilder line = new StringBuilder(); + for (int x = 0; x < COL_NUM; x++) { + line.append(cellField.getCell(x, y)); + if (x < COL_NUM - 1) { + line.append(";"); + } + } + saveState.add(line.toString()); + } + return saveState; + } /** * * @param lines of file representing saved world state @@ -266,49 +334,29 @@ public class Simulator extends Thread { */ public void generateRandom(float chanceOfLife) { - //TODO : complete method - Random random = new Random (); - for (int x = 0; x < COL_NUM; x++) { - for (int y = 0; y < LINE_NUM; y++) { - if (random.nextFloat() < chanceOfLife) { - setCell(x, y, 1); - } else { - setCell(x, y, 0); - } - } - } - } - - - /* - * Advice : - * as you should probably have a separate class - * representing the field of cells... - * maybe just make a constructor in there - * and use it here - */ - + cellField.generateRandom(chanceOfLife); + } public boolean isLoopingBorder() { - //TODO : complete method with proper return return loopingBorder; } public void toggleLoopingBorder() { - //TODO : complete method - loopingBorder = !loopingBorder; // Toggles the value of loopingBorder + loopingBorder = !loopingBorder; } public void setLoopDelay(int delay) { - //TODO : complete method loopDelay = delay; } public void toggleClickAction() { - //TODO : complete method - clickActionFlag =! clickActionFlag; + if(clickActionFlagValue getRule() { - //TODO : complete method with proper return - - return null; - } + // Returns an ArrayList of strings representing the rules of evolution for the world. + // Each line of the list contains the survival and birth rules separated by semicolons (;). + ArrayList ruleList = new ArrayList<>(); + String surviveRule = String.join(";", fieldSurviveValues.stream().map(String::valueOf).toArray(String[]::new)); + String birthRule = String.join(";", fieldBirthValues.stream().map(String::valueOf).toArray(String[]::new)); + ruleList.add(surviveRule); + ruleList.add(birthRule); + + return ruleList; + } + //loads survival and birth rules from a file into the program public void loadRule(ArrayList lines) { - if(lines.size()<=0) { - System.out.println("empty rule file"); + // Check if the rule file is empty + if (lines.size() <= 0) { + System.out.println("Empty rule file"); return; } - //TODO : remove previous rule (=emptying lists) + // Clear previous rule lists + fieldSurviveValues.clear(); + fieldBirthValues.clear(); + // Extract survival and birth rule lines from the file String surviveLine = lines.get(0); String birthLine = lines.get(1); + + // Split survival rule line into individual elements and add to the survival rule list String[] surviveElements = surviveLine.split(";"); - for(int x=0; x getAgentsSave() { - //TODO : Same idea as the other save method, but for agents - return null; + // Iterates through the list of agents and saves them according to their place in the AGENT_LIST + // Allows for more flexibility when adding new agents + + // Initialize a list to store the simplified representation of agents' positions + ArrayList agentsSave = new ArrayList<>(); + // Initialize a map to store the positions of agents based on their class types + Map, StringBuilder> agentPositions = new HashMap<>(); + + for (Class agentClass : AGENT_LIST) { + agentPositions.put(agentClass, new StringBuilder()); + } + + // Check if the agent's class is present in the AGENT_LIST + for (Agent agent : agents) { + if (AGENT_LIST.contains(agent.getClass())) { + StringBuilder positions = agentPositions.get(agent.getClass()); + positions.append(String.format("%d,%d;", agent.getX(), agent.getY())); + } + } + + // Construct the final list of agents' positions + for (Class agentClass : AGENT_LIST) { + StringBuilder positions = agentPositions.get(agentClass); + if (positions.length() == 0) { + // Add "-1" if the line is skipped + agentsSave.add("-1"); + } else { + // Remove the last semicolon + positions.deleteCharAt(positions.length() - 1); + agentsSave.add(positions.toString()); + } + } + + return agentsSave; } - public void loadAgents(ArrayList stringArray) { - //TODO : Same idea as other load methods, but for agent list - + public void loadAgents(ArrayList lines) { + agents.clear(); + // Iterate through each line in the list of strings representing agents' positions + int i = 0; + for (String line : lines) { + // Skip the line if it equals "-1" + if (line.equals("-1")) { + continue; + } + // Split the line into individual coordinates based on the ";" delimiter + String[] coordinates = line.split(";"); + + // Iterate through each coordinate + for (String coord : coordinates) { + String[] xy = coord.split(","); + int x = Integer.parseInt(xy[0]); + int y = Integer.parseInt(xy[1]); + + try { + // create an instance of an agent according to its place on the list + Class agentClass = AGENT_LIST.get(i); + Constructor constructor = agentClass.getConstructor(int.class, int.class); + Agent agent = (Agent) constructor.newInstance(x, y); + // Add the newly created agent to the list of agents + agents.add(agent); + } catch (Exception e) { + e.printStackTrace(); + } + } + // Increment the index to correspond to the position of the next agent class in the AGENT_LIST + i++; + } } + + /** * used by label in interface to show the active click action * @return String representation of click action */ + //determines and returns the name of the action associated with a click event public String clickActionName() { - // TODO : initially return "sheep" or "cell" - // depending on clickActionFlag - return ""; + // Check if the click action flag value indicates an action related to the cell itself + if (clickActionFlagValue == 0) { + return "cell";// Return the name of the action related to the cell + } else { + // Check if the click action flag value indicates an action related to an agent + if (clickActionFlagValue <= AGENT_LIST.size()) { + // Get the class of the agent associated with the click action + Class agentClass = AGENT_LIST.get(clickActionFlagValue - 1); + try { + // Create an instance of the agent to determine its type + Constructor constructor = agentClass.getConstructor(int.class, int.class); + Agent agent = (Agent) constructor.newInstance(0, 0); + return agent.type(); + } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { + e.printStackTrace(); + return "Unknown"; + } + } else { + throw new IllegalArgumentException("Invalid click action flag value"); + } + } + } + + public ArrayList getAgentsToRemove(){ + return agentsToRemove; + } + + public ArrayList getAgentsToAdd(){ + return agentsToAdd; } + + } diff --git a/src/backend/Wolf.java b/src/backend/Wolf.java new file mode 100644 index 0000000..46dfed4 --- /dev/null +++ b/src/backend/Wolf.java @@ -0,0 +1,81 @@ +package backend; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Random; + +public class Wolf extends Agent { + Random rand; + private int turnWithoutSheep; + + public Wolf(int x, int y) { + super(x, y, Color.black); + rand = new Random(); + turnWithoutSheep = 0; + } + + public boolean liveTurn(ArrayList neighbors, Simulator world) { + Iterator iterator = neighbors.iterator(); + boolean flag = false; + while (iterator.hasNext() && !flag) { + Agent neighbor = iterator.next(); + if (neighbor instanceof Sheep) { + // If sheep is found, move towards it and eat it + jumpTowards(neighbor.getX(), neighbor.getY()); + // Add the eaten sheep to the list of agents to be removed from the world + world.getAgentsToRemove().add(neighbor); + turnWithoutSheep = 0;// Reset the counter for turns without finding a sheep + flag = true;// Set the flag to true as a sheep has been found and eaten + } + } + // If no sheep found, move randomly + if(!flag) { + moveRandom(neighbors); + turnWithoutSheep += 1;// Increment the counter for turns without finding a sheep + } + + + if(turnWithoutSheep <= 30) { + return false; + }else { + return true; + } + } + + private void moveRandom(ArrayList neighbors) { + int direction = rand.nextInt(4); + if(direction == 0 && isAvailable(x+1 , y, neighbors)) { + x+=1; + } + if(direction == 1 && isAvailable(x , y+1, neighbors)) { + y+=1; + } + if(direction == 2 && isAvailable(x-1 , y, neighbors)) { + x-=1; + } + if(direction == 3 && isAvailable(x , y-1, neighbors)) { + y-=1; + } + } + + private boolean isAvailable(int x, int y, ArrayList neighbors) { + for (Agent neighbor : neighbors) { + if(neighbor.getX() ==x && neighbor.getY() ==y) { + return false; + } + } + return true; + } + + private void jumpTowards(int targetX, int targetY) { + x = targetX; + y = targetY; + } + + + + public String type() { + return "wolf"; + } +}