diff --git a/Rule/ConwayRule.csv b/Rule/ConwayRule.csv index 009a7ad..1d93ca8 100644 --- a/Rule/ConwayRule.csv +++ b/Rule/ConwayRule.csv @@ -1,2 +1,2 @@ -2;3 -3 \ No newline at end of file +[1, 3, 5, 8] +[3, 5, 7] diff --git a/src/backend/Agent.java b/src/backend/Agent.java index 8b11e23..f1fb639 100644 --- a/src/backend/Agent.java +++ b/src/backend/Agent.java @@ -7,34 +7,31 @@ public abstract class Agent { protected int x; protected int y; protected Color color; - + protected Agent(int x, int y, Color color) { this.x = x; 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; - int dist = (int) Math.floor(Math.sqrt(diffX*diffX+diffY*diffY)); - return dist neighbors, Simulator world); - - } diff --git a/src/backend/Grid.java b/src/backend/Grid.java new file mode 100644 index 0000000..b7c226c --- /dev/null +++ b/src/backend/Grid.java @@ -0,0 +1,105 @@ + +package backend; +import java.util.ArrayList; + +public class Grid { + // Private fields to store the grid data and dimensions + private int[][] cells; + private int width; + private int height; + + //Constructor to initialize the grid with given the width and height + public Grid(int width, int height) { + this.width = width; + this.height = height; + this.cells = new int[width][height]; + } + + + // Getter methods to retrieve the grid dimensions + public int getWidth() { + return width; + } + public int getHeight() { + return height; + } + + // Getter and setter methods for individual cells + public int getCell(int x, int y) { + return cells[x][y]; + } + public void setCell(int x, int y, int val) { + cells[x][y] = val; + } + + //Method to change the state of a cell (0=not colored, 1=colored) + public void toggleCell(int x, int y) { + cells[x][y] = (cells[x][y] == 0) ? 1 : 0; + } + + + + // Method to save the current state of the grid to an ArrayList of strings + public ArrayList saveState() { + ArrayList state = new ArrayList<>(); + for (int x = 0; x < width; x++) { + StringBuilder row = new StringBuilder(); + for (int y = 0; y < height; y++) { + row.append(cells[x][y]).append(";"); + } + state.add(row.toString()); + } + return state; + } + + // Method to load the state of the grid from an ArrayList of strings + public void loadState(ArrayList lines) { + for (int x = 0; x < lines.size(); x++) { + String[] values = lines.get(x).split(";"); + for (int y = 0; y < values.length; y++) { + cells[x][y] = Integer.parseInt(values[y]); + } + } + } + + // Method to update the cells of the grid based on game of life rules + public void updateCells(ArrayList surviveRules, ArrayList birthRules, boolean loopingBorder) { + int[][] newCells = new int[width][height]; + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int neighbors = countNeighbors(x, y, loopingBorder); + if (cells[x][y] == 1) { + newCells[x][y] = (surviveRules.contains(neighbors)) ? 1 : 0; + } else { + newCells[x][y] = (birthRules.contains(neighbors)) ? 1 : 0; + } + } + } + cells = newCells; + } + + + + + // Method to count the number of live neighbors of a cell + private int countNeighbors(int x, int y, boolean loopingBorder) { + int count = 0; + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + if (i == 0 && j == 0) + continue; + int nx = x + i; + int ny = y + j; + if (loopingBorder) { + nx = (nx + width) % width; + ny = (ny + height) % height; + } + if (nx >= 0 && nx < width && ny >= 0 && ny < height) { + count += cells[nx][ny]; + } + } + } + + return count; + } +} diff --git a/src/backend/Sheep.java b/src/backend/Sheep.java index ece8a18..bcf1a7b 100644 --- a/src/backend/Sheep.java +++ b/src/backend/Sheep.java @@ -4,56 +4,22 @@ import java.awt.Color; 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){ - //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 - super(x,y,Color.white); - // we give our sheep a hunger value of zero at birth + + public Sheep(int x, int y) { + super(x, y, Color.white); hunger = 0; - //we initialize the random number generator we will use to move randomly rand = new Random(); } - - /** - * action of the animal - * it can interact with the cells or with other animals - * as you wish - */ +//The method replaces the superclass method animal + @Override public boolean liveTurn(ArrayList neighbors, Simulator world) { - if(world.getCell(x, y)==1) { - world.setCell(x, y, 0); - } else { - hunger++; - } - this.moveRandom(); - return hunger>10; + // To make the sheep moving randomly + x += rand.nextInt(3) - 1; + y += rand.nextInt(3) - 1; + hunger++; + return hunger < 20; // The sheep dies if it reaches maximum hunger } - - private void moveRandom() { - int direction = rand.nextInt(4); - if(direction == 0) { - x+=1; - } - if(direction == 1) { - y+=1; - } - if(direction == 2) { - x-=1; - } - if(direction == 3) { - y-=1; - } - } - - } diff --git a/src/backend/Simulator.java b/src/backend/Simulator.java index 9774cd3..c668b9f 100644 --- a/src/backend/Simulator.java +++ b/src/backend/Simulator.java @@ -1,68 +1,66 @@ package backend; -import java.util.ArrayList; +import java.util.Random; +import java.util.ArrayList; import windowInterface.MyInterface; +// Simulator class that controls the lifecycle and rules of the simulation public class Simulator extends Thread { - private MyInterface mjf; - - private final int COL_NUM = 100; - private final int LINE_NUM = 100; - private final int LIFE_TYPE_NUM = 4; - //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 ArrayList agents; - + private World world; + private ArrayList surviveRules; + private ArrayList birthRules; private boolean stopFlag; private boolean pauseFlag; private boolean loopingBorder; - private boolean clickActionFlag; private int loopDelay = 150; + private int clickActionFlag; - //TODO : add missing attribute(s) - + //The constructor initializes the simulator with a user interface parameter public Simulator(MyInterface mjfParam) { - mjf = mjfParam; - stopFlag=false; - pauseFlag=false; - loopingBorder=false; - clickActionFlag=false; + this.mjf = mjfParam; + this.world = new World(100, 100); // Set world size to 100x100 + this.stopFlag = false; + this.pauseFlag = false; + this.loopingBorder = false; + this.clickActionFlag = 0; // 0 for cell, 1 for agent + this.surviveRules = new ArrayList<>(); + this.birthRules = new ArrayList<>(); + initializeDefaultRules(); + } - agents = new ArrayList(); - fieldBirthValues = new ArrayList(); - fieldSurviveValues = new ArrayList(); - - //TODO : add missing attribute initialization - - - - //Default rule : Survive always, birth never - for(int i =0; i<9; i++) { - fieldSurviveValues.add(i); + + // Initialization of the default survival rules for the simulation + private void initializeDefaultRules() { + for (int i = 0; i < 9; i++) { + surviveRules.add(i); } - } + // To retrieves neighbors of a specified agent in a certain radius + public ArrayList getNeighbors(Agent agent) { + ArrayList neighbors = new ArrayList<>(); + for (Agent other : world.getAgents()) { + if (other != agent && agent.isInArea(other.getX(), other.getY(), 1)) { // Assuming radius of 1 + neighbors.add(other); + } + } + return neighbors; + } + + // Getter methods for world dimensions public int getWidth() { - //TODO : replace with proper return - return 0; + return world.getGrid().getWidth(); } - public int getHeight() { - //TODO : replace with proper return - return 0; + return world.getGrid().getHeight(); } - //Should probably stay as is + // The main run method for the thread that handles simulation steps + @Override public void run() { - int stepCount=0; - while(!stopFlag) { + int stepCount = 0; + while (!stopFlag) { stepCount++; makeStep(); mjf.update(stepCount); @@ -71,7 +69,7 @@ public class Simulator extends Thread { } catch (InterruptedException e) { e.printStackTrace(); } - while(pauseFlag && !stopFlag) { + while (pauseFlag && !stopFlag) { try { Thread.sleep(loopDelay); } catch (InterruptedException e) { @@ -79,254 +77,159 @@ 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 - */ - - - - + world.processAgentTurns(this); + world.updateGrid(surviveRules, birthRules, loopingBorder); } - /* - * leave this as is - */ + + + // Stop the simulation public void stopSimu() { - stopFlag=true; + stopFlag = true; } - - /* - * method called when clicking pause button - */ + // Pause the simulation public void togglePause() { - // TODO : actually toggle the corresponding flag + pauseFlag = !pauseFlag; } + - /** - * method called when clicking on a cell in the interface - */ + // Handles cell click actions based on the current clickActionFlag public void clickCell(int x, int y) { - //TODO : complete method + if (clickActionFlag == 0) { + world.getGrid().toggleCell(x, y); + } else { + world.addAgent(new Sheep(x, y)); + } } + - /** - * get cell value in simulated world - * @param x coordinate of cell - * @param y coordinate of cell - * @return value of cell - */ + // To returns the state of a cell at a given coordinate public int getCell(int x, int y) { - //TODO : complete method with proper return - return 0; + return world.getGrid().getCell(x, y); } - /** - * - * @return list of Animals in simulated world - */ - public ArrayList getAnimals(){ - return agents; + + // To retrieves all animals in the simulation + public ArrayList getAnimals() { + return new ArrayList<>(world.getAgents()); } - /** - * selects Animals in a circular area of simulated world - * @param x center - * @param y center - * @param radius - * @return list of agents in area - */ - public ArrayList getNeighboringAnimals(int x, int y, int radius){ - ArrayList inArea = new ArrayList(); - for(int i=0;i getNeighboringAnimals(int x, int y, int radius) { + ArrayList inArea = new ArrayList<>(); + for (Agent agent : world.getAgents()) { + if (agent.isInArea(x, y, radius)) { inArea.add(agent); } } return inArea; } - - /** - * set value of cell - * @param x coord of cell - * @param y coord of cell - * @param val to set in cell - */ public void setCell(int x, int y, int val) { - //TODO : complete method + world.getGrid().setCell(x, y, val); } - - /** - * - * @return lines of file representing - * the simulated world in its present state - */ public ArrayList getSaveState() { - //TODO : complete method with proper return - return null; + return world.getGrid().saveState(); } - /** - * - * @param lines of file representing saved world state - */ public void loadSaveState(ArrayList lines) { - /* - * First some checks that the file is usable - * We call early returns in conditions like this - * "Guard clauses", as they guard the method - * against unwanted inputs - */ - if(lines.size()<=0) { - return; + world.getGrid().loadState(lines); + } + + + // Generates random initial states based on a given life chance + public void generateRandom(float chanceOfLife) { + Random rand = new Random(); + for (int x = 0; x < getWidth(); x++) { + for (int y = 0; y < getHeight(); y++) { + world.getGrid().setCell(x, y, (rand.nextFloat() < chanceOfLife) ? 1 : 0); + } } - String firstLine = lines.get(0); - String[] firstLineElements = firstLine.split(";"); - if(firstLineElements.length<=0) { - return; - } - /* - * now we fill in the world - * with the content of the file - */ - for(int y =0; y getRule() { + ArrayList rules = new ArrayList<>(); + rules.add(String.join(";", surviveRules.toString())); + rules.add(String.join(";", birthRules.toString())); + return rules; + } + + + public void loadRule(ArrayList lines) { + surviveRules.clear(); + birthRules.clear(); + if (!lines.isEmpty()) { + // Handle the survival rules from the first line + String[] survivalNumbers = extractNumbers(lines.get(0)); + for (String num : survivalNumbers) { + if (!num.isEmpty()) { + surviveRules.add(Integer.parseInt(num.trim())); + } + } + // Handle the birth rules from the second line (if it exists) + if (lines.size() > 1) { + String[] birthNumbers = extractNumbers(lines.get(1)); + for (String num : birthNumbers) { + if (!num.isEmpty()) { + birthRules.add(Integer.parseInt(num.trim())); + } + } } } } - /** - * called by button, with slider providing the argument - * makes a new world state with random cell states - * @param chanceOfLife the chance for each cell - * to be alive in new state - */ - public void generateRandom(float chanceOfLife) { - //TODO : complete method - /* - * 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 - */ - } - public boolean isLoopingBorder() { - //TODO : complete method with proper return - return false; - } - public void toggleLoopingBorder() { - //TODO : complete method - - } - - public void setLoopDelay(int delay) { - //TODO : complete method - } - - public void toggleClickAction() { - //TODO : complete method + private String[] extractNumbers(String line) { + line = line.trim().replaceAll("[\\[\\]]", ""); // Remove brackets if present + return line.split(",\\s*|;\\s*"); // Split by comma or semicolon with optional whitespace } - /** - * prepare the content of a file saving present ruleSet - * as you might want to save a state, - * initialy written in this class constructor - * as a file for future use - * @return File content as an ArrayList of Lines (String) - * @see loadRule for inverse process - */ - public ArrayList getRule() { - //TODO : complete method with proper return - - return null; - } - - public void loadRule(ArrayList lines) { - if(lines.size()<=0) { - System.out.println("empty rule file"); - return; - } - //TODO : remove previous rule (=emptying lists) - - - String surviveLine = lines.get(0); - String birthLine = lines.get(1); - String[] surviveElements = surviveLine.split(";"); - for(int x=0; x getAgentsSave() { - //TODO : Same idea as the other save method, but for agents - return null; + ArrayList agentsList = new ArrayList<>(); + for (Agent agent : world.getAgents()) { + agentsList.add(agent.getClass().getSimpleName() + "," + agent.getX() + "," + agent.getY()); + } + return agentsList; } - public void loadAgents(ArrayList stringArray) { - //TODO : Same idea as other load methods, but for agent list - + + public void loadAgents(ArrayList lines) { + world.getAgents().clear(); + for (String line : lines) { + String[] data = line.split(";"); // Split using semicolons + String type = data[0].trim(); // Trim whitespace + int x = Integer.parseInt(data[1].trim().replace(",", "")); // Remove commas and trim spaces + int y = Integer.parseInt(data[2].trim().replace(",", "")); // Remove commas and trim spaces + if (type.equals("Sheep")) { + world.addAgent(new Sheep(x, y)); + } else if (type.equals("Wolf")) { + world.addAgent(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 == 0 ? "cell" : "sheep"; } - } diff --git a/src/backend/Wolf.java b/src/backend/Wolf.java new file mode 100644 index 0000000..af68fa9 --- /dev/null +++ b/src/backend/Wolf.java @@ -0,0 +1,44 @@ + +// We created a class for another animal : wolves +// They eat sheeps + +package backend; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.Random; + +public class Wolf extends Agent { + int hunger; + Random rand; + + + public Wolf(int x, int y) { + // We call the constructor of the superClass animal + // Ande we decided that the wolves are gray + super(x, y, Color.gray); + // At birth, their hunger value is zero + hunger = 0; + //And we initialize the random number generator for their random movement + rand = new Random(); + } + + //The method replaces the superclass method animal + @Override + public boolean liveTurn(ArrayList neighbors, Simulator world) { + for (Agent neighbor : neighbors) { + if (neighbor instanceof Sheep) { + // To reset their hunger after eating + hunger = 0; + // And wolf survives by eating sheep + return true; + } + } + // To make them move randomly + x += rand.nextInt(3) - 1; + y += rand.nextInt(3) - 1; + hunger++; + //The wolve dies if it starves + return hunger < 10; + } +} diff --git a/src/backend/World.java b/src/backend/World.java new file mode 100644 index 0000000..6035072 --- /dev/null +++ b/src/backend/World.java @@ -0,0 +1,61 @@ +package backend; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + + +public class World { + // Private fields to store the grid and the agents + private Grid grid; + private Set agents; + + + // Constructor to initialize the world with the grid and an empty set of agents + public World(int width, int height) { + this.grid = new Grid(width, height); + this.agents = new HashSet<>(); + } + + + + //Getter method for the grid + public Grid getGrid() { + return grid; + } + //Getter method for the set of agents + public Set getAgents() { + return agents; + } + + + // Method to add an agent to the world + public void addAgent(Agent agent) { + agents.add(agent); + } + // Method to remove it + public void removeAgent(Agent agent) { + agents.remove(agent); + } + + + + // Method to update the grid based on game of life rules + public void updateGrid(ArrayList surviveRules, ArrayList birthRules, boolean loopingBorder) { + grid.updateCells(surviveRules, birthRules, loopingBorder); + } + // Method to process turns for all agents and return the surviving ones + public Set processAgentTurns(Simulator simulator) { + Set survivingAgents = new HashSet<>(); + for (Agent agent : agents) { + + // Call liveTurn method for each agent and add surviving ones to the set + if (agent.liveTurn(simulator.getNeighbors(agent), simulator)) { + survivingAgents.add(agent); + } + } + + + + return survivingAgents; + } +}