diff --git a/FinalProject/src/backend/Cell.java b/FinalProject/src/backend/Cell.java new file mode 100644 index 0000000..b611b1d --- /dev/null +++ b/FinalProject/src/backend/Cell.java @@ -0,0 +1,29 @@ +package backend; + + +public class Cell { + private int state; + + public Cell(int state) { + this.setState(state); + } + + public int getState() { + return state; + } + + public void setState(int state) { + this.state = state; + if (state == 4) { + this.state = -3; + } + } + + public void age() { + if (state > 0 && state < 4) { + state = Math.min(state + 1, 3); + } else if (state < 0) { + this.state++; + } + } +} diff --git a/FinalProject/src/backend/Grid.java b/FinalProject/src/backend/Grid.java new file mode 100644 index 0000000..aa1534f --- /dev/null +++ b/FinalProject/src/backend/Grid.java @@ -0,0 +1,44 @@ +package backend; + + +public class Grid { + private int[][] grid; + + // Constructor to initialize the grid with given dimensions + public Grid(int rows, int columns) { + grid = new int[rows][columns]; + } + + // Method to set the value of a cell at specified coordinates + public void setValue(int x, int y, int value) { + grid[x][y] = value; + } + + // Method to get the value of a cell at specified coordinates + public int getValue(int x, int y) { + return grid[x][y]; + } + + // Method to get the number of rows in the grid + public int getRowCount() { + return grid.length; + } + + // Method to get the number of columns in the grid + public int getColumnCount() { + return grid[0].length; + } + + // Additional method to get the Cell object for logic purposes + public Cell getCell(int x, int y) { + return new Cell(grid[x][y]); + } + + // Additional method to set the Cell object state + public void setCell(int x, int y, Cell cell) { + grid[x][y] = cell.getState(); + } +} + + + diff --git a/FinalProject/src/backend/Simulator.java b/FinalProject/src/backend/Simulator.java index 9774cd3..3b77a52 100644 --- a/FinalProject/src/backend/Simulator.java +++ b/FinalProject/src/backend/Simulator.java @@ -1,332 +1,458 @@ package backend; import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import windowInterface.MyInterface; 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 boolean stopFlag; - private boolean pauseFlag; - private boolean loopingBorder; - private boolean clickActionFlag; - private int loopDelay = 150; + 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 List> survivalRules; + private List> birthRules; + + private ArrayList agents; + + private boolean stopFlag; + private boolean pauseFlag; + private boolean loopingBorder; + private String clickActionFlag; + private int loopDelay = 150; + + private Grid grid; - //TODO : add missing attribute(s) + //TODO : add missing attribute(s) - public Simulator(MyInterface mjfParam) { - mjf = mjfParam; - stopFlag=false; - pauseFlag=false; - loopingBorder=false; - clickActionFlag=false; + public Simulator(MyInterface mjfParam) { + mjf = mjfParam; + stopFlag=false; + pauseFlag=false; + loopingBorder=false; + clickActionFlag="Cells"; - agents = new ArrayList(); - fieldBirthValues = new ArrayList(); - fieldSurviveValues = new ArrayList(); + agents = new ArrayList(); - //TODO : add missing attribute initialization - - - - //Default rule : Survive always, birth never - for(int i =0; i<9; i++) { - fieldSurviveValues.add(i); - } - - } + // Initialize rules + survivalRules = new ArrayList<>(); + birthRules = new ArrayList<>(); - public int getWidth() { - //TODO : replace with proper return - return 0; - } + // Define survival rules for each cell state + survivalRules.add(Arrays.asList()); // State 0 (Dead cells don't survive) + survivalRules.add(Arrays.asList(2, 3)); // State 1 (Young cells) + survivalRules.add(Arrays.asList(2, 3, 4)); // State 2 (Middle-aged cells) + survivalRules.add(Arrays.asList(1, 2, 3)); // State 3 (Old cells) - public int getHeight() { - //TODO : replace with proper return - return 0; - } + // Define birth rules for each cell state + birthRules.add(Arrays.asList(3)); // State 0 (Dead cells birth to young cells) + birthRules.add(Arrays.asList()); // State 1 (Young cells don't give birth) + birthRules.add(Arrays.asList()); // State 2 (Middle-aged cells don't give birth) + birthRules.add(Arrays.asList(4)); // State 3 (Old cells give birth to young cells) - //Should probably stay as is - public void run() { - int stepCount=0; - while(!stopFlag) { - stepCount++; - makeStep(); - mjf.update(stepCount); - try { - Thread.sleep(loopDelay); - } catch (InterruptedException e) { - e.printStackTrace(); - } - while(pauseFlag && !stopFlag) { - try { - Thread.sleep(loopDelay); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } + grid = new Grid(LINE_NUM, COL_NUM); + + } - } + public int getWidth() { + return LINE_NUM; + } - /** - * 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 - */ - - - - - } - - /* - * leave this as is - */ - public void stopSimu() { - stopFlag=true; - } - - /* - * method called when clicking pause button - */ - public void togglePause() { - // TODO : actually toggle the corresponding flag - } - - /** - * method called when clicking on a cell in the interface - */ - public void clickCell(int x, int y) { - //TODO : complete method - } - - /** - * get cell value in simulated world - * @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 list of Animals in simulated world - */ - public ArrayList getAnimals(){ - return agents; - } - /** - * 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 getSaveState() { - //TODO : complete method with proper return - return null; - } - /** - * - * @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; - } - 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() { - //TODO : complete method with proper return - - return null; - } + /** + * 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 + for(Agent agent : agents) { + ArrayList neighbors = + this.getNeighboringAnimals( + agent.getX(), + agent.getY(), + ANIMAL_AREA_RADIUS); + if(!agent.liveTurn( + neighbors, + this)) { + agents.remove(agent); + } + } - 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; - } + // Then evolution of the field + Grid nextGrid = new Grid(LINE_NUM, COL_NUM); - public void loadAgents(ArrayList stringArray) { - //TODO : Same idea as other load methods, but for agent list - - } + for (int i = 0; i < LINE_NUM; i++) { + for (int j = 0; j < COL_NUM; j++) { + Cell cell = grid.getCell(i, j); + int liveNeighbors = countLiveNeighbors(i, j); + int cellState = cell.getState(); - /** - * 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 ""; - } + if (cellState > 0 && cellState < 4) { // For living cells + if (survivalRules.get(cellState).contains(liveNeighbors)) { + cell.age(); + nextGrid.setCell(i, j, cell); + } else { + nextGrid.setValue(i, j, 0); + } + } else if (cellState < 0) { + cell.age(); + + if (cell.getState() == 0) { + nextGrid.setValue(i, j, 0); + } else { + nextGrid.setCell(i, j, cell); + } + } else { + if (birthRules.get(cellState).contains(liveNeighbors)) { + nextGrid.setValue(i, j, 1); + } + } + } + } + + grid = nextGrid; + } + + /* + * method called to count living cells around one + */ + public int countLiveNeighbors(int x, int y) { + int liveNeighbors = 0; + + for (int i = -LIFE_AREA_RADIUS; i <= LIFE_AREA_RADIUS; i++) { + for (int j = -LIFE_AREA_RADIUS; j <= LIFE_AREA_RADIUS; j++) { + if (i == 0 && j == 0) continue; + + int neighborX = x + i; + int neighborY = y + j; + + if (loopingBorder) { + if (neighborX < 0) neighborX = LINE_NUM - 1; + else if (neighborX >= LINE_NUM) neighborX = 0; + + if (neighborY < 0) neighborY = COL_NUM - 1; + else if (neighborY >= COL_NUM) neighborY = 0; + } + + if (neighborX >= 0 && neighborX < LINE_NUM && neighborY >= 0 && neighborY < COL_NUM) { + if (grid.getValue(neighborX, neighborY) > 0) { + liveNeighbors++; + } + } + } + } + + return liveNeighbors; + } + + + + /* + * leave this as is + */ + public void stopSimu() { + stopFlag=true; + } + + /* + * method called when clicking pause button + */ + public void togglePause() { + if (pauseFlag == true ) { + pauseFlag=false; + } + else { + pauseFlag=true; + } + } + + /** + * method called when clicking on a cell in the interface + * Cycles through cell states: 0 (dead), 1 (young), 2 (middle-aged), 3 (old). + */ + public void clickCell(int x, int y) { + if (clickActionFlag.equals("Cells")) { + int currentValue = grid.getValue(x, y); + int nextValue = (currentValue + 1) % 5; // Change to 5 to include infected state + this.setCell(x, y, nextValue); + } else if (clickActionFlag.equals("Virus")) { + agents.add(new Virus(x, y)); // Add a new Virus agent at the clicked location + } + } + + /** + * get cell value in simulated world + * @param x coordinate of cell + * @param y coordinate of cell + * @return value of cell + */ + public int getCell(int x, int y) { + return grid.getValue(x, y); + } + /** + * + * @return list of Animals in simulated world + */ + public ArrayList getAnimals(){ + return agents; + } + /** + * 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 getSaveState() { + ArrayList saveState = new ArrayList<>(); + + for (int i = 0; i < LINE_NUM; i++) { + StringBuilder line = new StringBuilder(); + for (int j = 0; j < COL_NUM; j++) { + line.append(grid.getValue(i, j)); + if (j < COL_NUM - 1) { + line.append(";"); + } + } + saveState.add(line.toString()); + } + return 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; + } + 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 rule = new ArrayList<>(); + + // Add survival rules for each cell state + for (List stateRules : survivalRules) { + rule.add(String.join(";", stateRules.stream().map(Object::toString).toArray(String[]::new))); + } + + // Add birth rules for each cell state + for (List stateRules : birthRules) { + rule.add(String.join(";", stateRules.stream().map(Object::toString).toArray(String[]::new))); + } + + return rule; + } + + public void loadRule(ArrayList lines) { + if (lines.size() != LIFE_TYPE_NUM * 2) { + System.out.println("Invalid rule file format"); + return; + } + + survivalRules.clear(); + birthRules.clear(); + + for (int i = 0; i < LIFE_TYPE_NUM; i++) { + String survivalLine = lines.get(i); + String[] survivalParts = survivalLine.split(";"); + ArrayList survivalValues = new ArrayList<>(); + for (String part : survivalParts) { + String[] survivalElements = part.split(","); + for (String elem : survivalElements) { + if (!elem.isEmpty()) { // Skip empty elements + int value = Integer.parseInt(elem); + survivalValues.add(value); + } + } + } + survivalRules.add(survivalValues); + } + + for (int i = LIFE_TYPE_NUM; i < LIFE_TYPE_NUM * 2; i++) { + String birthLine = lines.get(i); + String[] birthParts = birthLine.split(";"); + ArrayList birthValues = new ArrayList<>(); + for (String part : birthParts) { + String[] birthElements = part.split(","); + for (String elem : birthElements) { + if (!elem.isEmpty()) { // Skip empty elements + int value = Integer.parseInt(elem); + birthValues.add(value); + } + } + } + birthRules.add(birthValues); + } + } + + + public ArrayList getAgentsSave() { + ArrayList saveData = new ArrayList<>(); + for (Agent agent : agents) { + saveData.add(agent.getClass().getSimpleName() + "," + agent.getX() + "," + agent.getY()); + } + return saveData; + } + + public void loadAgents(ArrayList stringArray) { + agents.clear(); + for (String line : stringArray) { + String[] parts = line.split(","); + String type = parts[0]; + int x = Integer.parseInt(parts[1]); + int y = Integer.parseInt(parts[2]); + if (type.equals("Virus")) { + agents.add(new Virus(x, y)); + } + } + } + + /** + * used by label in interface to show the active click action + * @return String representation of click action + */ + public String clickActionName() { + if (clickActionFlag.equals("Cells")) { + return "Cells"; + } else { + return "Virus"; + } + } } diff --git a/FinalProject/src/backend/Virus.java b/FinalProject/src/backend/Virus.java new file mode 100644 index 0000000..8c889bd --- /dev/null +++ b/FinalProject/src/backend/Virus.java @@ -0,0 +1,39 @@ +package backend; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.Random; + + +public class Virus extends Agent { + Random rand; + + Virus(int x, int y){ + super(x, y, Color.red); + rand = new Random(); + } + + public boolean liveTurn(ArrayList neighbors, Simulator world) { + if(world.getCell(x, y) > 0 && world.getCell(x, y) < 4) { + world.setCell(x, y, 4); // Infect cell + } + this.moveRandom(); + return true; + } + + 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/FinalProject/src/windowInterface/JPanelDraw.java b/FinalProject/src/windowInterface/JPanelDraw.java index a210363..eab0da4 100644 --- a/FinalProject/src/windowInterface/JPanelDraw.java +++ b/FinalProject/src/windowInterface/JPanelDraw.java @@ -13,82 +13,88 @@ import backend.Simulator; public class JPanelDraw extends JPanel { - private static final long serialVersionUID = 1L; - private Simulator mySimu; - private MyInterface interfaceGlobal; + private static final long serialVersionUID = 1L; + private Simulator mySimu; + private MyInterface interfaceGlobal; - public JPanelDraw(MyInterface itf) { - super(); - mySimu = null; - interfaceGlobal = itf; - addMouseListener(new MouseAdapter() { - public void mousePressed(MouseEvent me) { - // System.out.println(me); - if(mySimu == null) { - interfaceGlobal.instantiateSimu(); - } - int x = (me.getX()*mySimu.getWidth())/getWidth(); - int y = (me.getY()*mySimu.getHeight())/getHeight(); - mySimu.clickCell(x,y); - repaint(); - } - }); - } - + public JPanelDraw(MyInterface itf) { + super(); + mySimu = null; + interfaceGlobal = itf; + addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent me) { + // System.out.println(me); + if(mySimu == null) { + interfaceGlobal.instantiateSimu(); + } + int x = (me.getX()*mySimu.getWidth())/getWidth(); + int y = (me.getY()*mySimu.getHeight())/getHeight(); + mySimu.clickCell(x,y); + repaint(); + } + }); + } + - public void setSimu(Simulator simu) { - mySimu = simu; - } + public void setSimu(Simulator simu) { + mySimu = simu; + } - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - this.setBackground(Color.black); - if (mySimu != null) { - // Draw Interface from state of simulator - float cellWidth = (float)this.getWidth()/(float)mySimu.getWidth(); - float cellHeight = (float)this.getHeight()/(float)mySimu.getHeight(); - g.setColor(Color.gray); - for(int x=0; x= -3 && cellContent < 0) { + g.setColor(Color.red); + } + g.fillRect( + (int) Math.round(x*cellWidth), + (int) Math.round(y*cellHeight), + (int) Math.round(cellWidth), + (int) Math.round(cellHeight) + ); + } + } + for (Agent animal:mySimu.getAnimals()){ + g.setColor(animal.getDisplayColor()); + g.fillOval((int)Math.round(animal.getX()*cellWidth), + (int)Math.round(animal.getY()*cellHeight), + (int)Math.round(cellWidth/2), + (int)Math.round(cellHeight/2)); + } + } + } -} +} \ No newline at end of file