Final version

This commit is contained in:
FyloZ 2022-06-19 21:17:41 -04:00
parent d4af46450c
commit 45774f8c59
Signed by: william
GPG Key ID: 835378AE9AF4AE97
15 changed files with 106 additions and 107 deletions

View File

@ -18,11 +18,11 @@ public class ElementListIterator implements Iterator<Element> {
public boolean hasNext() { public boolean hasNext() {
if (position >= nodes.getLength()) return false; if (position >= nodes.getLength()) return false;
// Check if there is a remaining element node // Vérifie s'il reste un nœud élément
for (int i = position; i < nodes.getLength(); i++) { for (int i = position; i < nodes.getLength(); i++) {
Node node = nodes.item(i); Node node = nodes.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) return true; if (isElementNode(node)) return true;
} }
return false; return false;
@ -35,7 +35,11 @@ public class ElementListIterator implements Iterator<Element> {
Node node = nodes.item(position); Node node = nodes.item(position);
position++; position++;
if (node.getNodeType() == Node.ELEMENT_NODE) return (Element) node; if (isElementNode(node)) return (Element) node;
return next(); return next();
} }
private static boolean isElementNode(Node node) {
return node.getNodeType() == Node.ELEMENT_NODE;
}
} }

View File

@ -19,7 +19,12 @@ public abstract class Building {
} }
public abstract void processInput(Component input); public abstract void processInput(Component input);
public abstract Optional<Component> update();
/**
* Continue la production.
* @return Le composant produit. Si le bâtiment n'a aucun produit fini ce tour ci, l'Optional retourné sera vide.
*/
public abstract Optional<Component> produce();
public abstract BuildingMetadata getMetadata(); public abstract BuildingMetadata getMetadata();
public int getId() { public int getId() {
@ -41,8 +46,4 @@ public abstract class Building {
public BuildingState getState() { public BuildingState getState() {
return state; return state;
} }
public void setState(BuildingState state) {
this.state = state;
}
} }

View File

@ -1,63 +0,0 @@
package simulation;
public class ComponentRoute {
public static final int SPEED = 3;
private final Component component;
private final Building outputBuilding;
private final int directionX;
private final int directionY;
private int x;
private int y;
public ComponentRoute(Component component, Building outputBuilding, int x, int y) {
this.component = component;
this.outputBuilding = outputBuilding;
this.x = x;
this.y = y;
directionX = normalizedDirection(x, outputBuilding.getX());
directionY = normalizedDirection(y, outputBuilding.getY());
}
private int normalizedDirection(int from, int to) {
return Integer.compare(to, from) * SPEED;
}
public void updatePosition() {
x += directionX;
y += directionY;
}
public void sendToOutputBuilding() {
outputBuilding.processInput(component);
}
public boolean isTransitFinished() {
boolean xSame = directionX == 0 ||
(directionX > 0 && x >= outputBuilding.getX()) ||
(directionX < 0 && x <= outputBuilding.getX());
boolean ySame = directionY == 0 ||
(directionY > 0 && y >= outputBuilding.getY()) ||
(directionY < 0 && y <= outputBuilding.getY());
return xSame && ySame;
}
public Component getComponent() {
return component;
}
public Building getOutputBuilding() {
return outputBuilding;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}

View File

@ -12,31 +12,40 @@ public class Factory extends Building implements WarehouseObserver {
private final FactoryMetadata metadata; private final FactoryMetadata metadata;
private final Map<ComponentType, Integer> inputsCount = new HashMap<>(); private final Map<ComponentType, Integer> inputsCount = new HashMap<>();
private float productionInterval;
private boolean isProductionStarted; private boolean isProductionStarted;
private boolean warehouseFull; private boolean haltProduction;
private long productionTicks = 0L; private int productionTicks = 0;
public Factory(int id, String type, int x, int y, FactoryMetadata metadata) { public Factory(int id, String type, int x, int y, FactoryMetadata metadata) {
super(id, type, x, y); super(id, type, x, y);
this.metadata = metadata; this.metadata = metadata;
this.productionInterval = metadata.getProductionInterval();
// Les usines sans entrées devraient toujours produire // Les usines sans entrées doivent toujours produire
isProductionStarted = metadata.getInputs().size() == 0; isProductionStarted = metadata.getInputs().size() == 0;
if (isProductionStarted) { if (isProductionStarted) {
productionTicks = metadata.getProductionInterval(); productionTicks = (int) productionInterval;
} }
} }
@Override @Override
public Optional<Component> update() { public Optional<Component> produce() {
if (!isProductionStarted || (productionTicks == 0 && warehouseFull)) return Optional.empty(); if (!isProductionStarted ||
(productionTicks == 0 && haltProduction)) { // La production en cours ne doit pas s'arrêter, mais l'usine ne doit pas produire un nouveau composant
return Optional.empty();
}
productionTicks++; productionTicks++;
if (productionTicks < metadata.getProductionInterval()) { if (productionTicks < productionInterval) {
// La production n'est pas finie
updateState(); updateState();
return Optional.empty(); return Optional.empty();
} }
// La production est finie
// Réinitialise la progression de la production et défini l'état de l'usine à vide.
// Retourne le composant produit
if (metadata.getInputs().size() > 0) { if (metadata.getInputs().size() > 0) {
isProductionStarted = false; isProductionStarted = false;
} }
@ -50,10 +59,13 @@ public class Factory extends Building implements WarehouseObserver {
@Override @Override
public void processInput(Component input) { public void processInput(Component input) {
int inputCount = 0; int inputCount = 0;
// S'assure qu'une entrée pour ce type de composant existe.
if (inputsCount.containsKey(input.getType())) { if (inputsCount.containsKey(input.getType())) {
inputCount = inputsCount.get(input.getType()); inputCount = inputsCount.get(input.getType());
} }
// Augmente la quantité de ce type de composant en stock.
inputsCount.put(input.getType(), inputCount + 1); inputsCount.put(input.getType(), inputCount + 1);
if (hasEnoughComponents()) { if (hasEnoughComponents()) {
@ -67,12 +79,33 @@ public class Factory extends Building implements WarehouseObserver {
} }
@Override @Override
public void updateWarehouseState(boolean full) { public void updateWarehouseState(BuildingState state) {
warehouseFull = full; updateProductionSpeed(state);
}
private void updateProductionSpeed(BuildingState state) {
int originalProductionInterval = metadata.getProductionInterval();
// Augmente le temps de production selon l'état de l'entrepôt
switch (state) {
case EMPTY -> {
haltProduction = false;
productionInterval = originalProductionInterval;
}
case ONE_THIRD -> {
haltProduction = false;
productionInterval = originalProductionInterval * 1.33333f;
}
case TWO_THIRD -> {
haltProduction = false;
productionInterval = originalProductionInterval * 2.66666f;
}
default -> haltProduction = true;
}
} }
private void updateState() { private void updateState() {
float productionRatio = productionTicks / (float) metadata.getProductionInterval(); float productionRatio = productionTicks / productionInterval;
if (productionRatio >= (2 / 3f)) { if (productionRatio >= (2 / 3f)) {
state = BuildingState.FULL; state = BuildingState.FULL;
} else if (productionRatio >= (1 / 3f)) { } else if (productionRatio >= (1 / 3f)) {
@ -84,7 +117,8 @@ public class Factory extends Building implements WarehouseObserver {
private boolean hasEnoughComponents() { private boolean hasEnoughComponents() {
for (FactoryInput input : metadata.getInputs()) { for (FactoryInput input : metadata.getInputs()) {
if (!inputsCount.containsKey(input.getType()) || inputsCount.get(input.getType()) < input.getQuantity()) { if (!inputsCount.containsKey(input.getType()) ||
inputsCount.get(input.getType()) < input.getQuantity()) {
return false; return false;
} }
} }
@ -93,6 +127,7 @@ public class Factory extends Building implements WarehouseObserver {
} }
private void startProduction() { private void startProduction() {
// Retire les composants nécessaires des entrées
for (FactoryInput input : metadata.getInputs()) { for (FactoryInput input : metadata.getInputs()) {
int inputCount = inputsCount.get(input.getType()); int inputCount = inputsCount.get(input.getType());
inputsCount.put(input.getType(), inputCount - input.getQuantity()); inputsCount.put(input.getType(), inputCount - input.getQuantity());

View File

@ -3,7 +3,7 @@ package simulation;
import java.util.Random; import java.util.Random;
public class RandomSellStrategy implements SellStrategy { public class RandomSellStrategy implements SellStrategy {
private static final int SELL_CHANCE_PERCENT = 1; private static final int SELL_CHANCE_PERCENT = 1; // 1% de chance par tour
private final Random random = new Random(); private final Random random = new Random();

View File

@ -1,5 +1,8 @@
package simulation; package simulation;
public interface SellStrategy { public interface SellStrategy {
/**
* @return S'il est possible de vendre ce tour ci.
*/
boolean shouldSell(); boolean shouldSell();
} }

View File

@ -1,12 +1,12 @@
package simulation; package simulation;
public class TimedSellStrategy implements SellStrategy { public class TimedSellStrategy implements SellStrategy {
private static final int SELL_INTERVAL = 300; private static final int SELL_INTERVAL = 1000;
private long tick = 0L; private long turn = 0L;
@Override @Override
public boolean shouldSell() { public boolean shouldSell() {
return ++tick % SELL_INTERVAL == 0; return ++turn % SELL_INTERVAL == 0;
} }
} }

View File

@ -23,7 +23,7 @@ public class Warehouse extends Building implements WarehouseSubject {
} }
@Override @Override
public Optional<Component> update() { public Optional<Component> produce() {
if (planeCount > 0 && sellStrategy.shouldSell()) { if (planeCount > 0 && sellStrategy.shouldSell()) {
sellPlane(); sellPlane();
} }
@ -71,13 +71,12 @@ public class Warehouse extends Building implements WarehouseSubject {
observers.add(observer); observers.add(observer);
} }
public void detach(WarehouseObserver observer) { public void dettach(WarehouseObserver observer) {
observers.remove(observer); observers.remove(observer);
} }
public void notifyObservers() { public void notifyObservers() {
boolean isFull = planeCount >= capacity; observers.forEach(o -> o.updateWarehouseState(state));
observers.forEach(o -> o.updateWarehouseState(isFull));
} }
@Override @Override

View File

@ -4,7 +4,7 @@ public interface WarehouseObserver {
/** /**
* Méthode appelée lorsque l'état d'un entrepot change. * Méthode appelée lorsque l'état d'un entrepot change.
* *
* @param full Si l'entrepot est plein ou non * @param full Si l'entrepôt est plein ou non
*/ */
void updateWarehouseState(boolean full); void updateWarehouseState(BuildingState state);
} }

View File

@ -2,6 +2,6 @@ package simulation;
public interface WarehouseSubject { public interface WarehouseSubject {
void attach(WarehouseObserver observer); void attach(WarehouseObserver observer);
void detach(WarehouseObserver observer); void dettach(WarehouseObserver observer);
void notifyObservers(); void notifyObservers();
} }

View File

@ -4,7 +4,7 @@ import javax.swing.*;
public class Environment extends SwingWorker<Object, String> { public class Environment extends SwingWorker<Object, String> {
private static final int DELAY = 100; private static final int DELAY = 100;
public static final String TICK_PROPERTY_NAME = "tick"; public static final String TURN_PROPERTY_NAME = "turn";
private boolean active = true; private boolean active = true;
private long tick = 0L; private long tick = 0L;
@ -14,7 +14,7 @@ public class Environment extends SwingWorker<Object, String> {
while (active) { while (active) {
Thread.sleep(DELAY); Thread.sleep(DELAY);
firePropertyChange(TICK_PROPERTY_NAME, tick, ++tick); firePropertyChange(TURN_PROPERTY_NAME, tick, ++tick);
} }
return null; return null;
} }

View File

@ -33,8 +33,11 @@ public class MainPanel extends JLayeredPane {
simulationData = configuration.getSimulationData(); simulationData = configuration.getSimulationData();
buildingsById.clear(); buildingsById.clear();
componentIcons.clear();
removeAll();
repaint();
int routesOffset = 0; int routesOffset = 0; // Cet offset permet aux lignes de routes d'être au centre des icônes des bâtiments.
for (Building building : simulationData.getBuildings()) { for (Building building : simulationData.getBuildings()) {
BuildingIcon icon = this.initializeBuilding(building); BuildingIcon icon = this.initializeBuilding(building);
routesOffset = icon.getWidth() / 2; routesOffset = icon.getWidth() / 2;
@ -72,27 +75,37 @@ public class MainPanel extends JLayeredPane {
if (configuration == null) return; if (configuration == null) return;
updateBuildings(); updateBuildings();
updateComponentIcons();
}
private void updateComponentIcons() {
Collection<ComponentIcon> removedIcons = new ArrayList<>(); Collection<ComponentIcon> removedIcons = new ArrayList<>();
for (ComponentIcon icon : componentIcons) { componentIcons.forEach(i -> {
if (icon.isAtDestination()) { i.updatePosition();
removedIcons.add(icon);
} boolean destinationReached = i.isAtDestination();
if (destinationReached) {
removedIcons.add(i);
} }
});
removedIcons.forEach(i -> { removedIcons.forEach(i -> {
componentIcons.remove(i); componentIcons.remove(i);
remove(i); remove(i);
Building outputBuilding = getBuildingById(i.getDestinationId()); pushComponentToBuilding(i.getComponent(), i.getDestinationId());
outputBuilding.processInput(i.getComponent());
}); });
} }
private void pushComponentToBuilding(Component c, int buildingId) {
Building outputBuilding = getBuildingById(buildingId);
outputBuilding.processInput(c);
}
private void updateBuildings() { private void updateBuildings() {
simulationData.getBuildings().forEach(b -> { simulationData.getBuildings().forEach(b -> {
b.update().ifPresent(c -> addComponentRoute(c, b)); b.produce().ifPresent(c -> addComponentRoute(c, b));
}); });
} }

View File

@ -36,7 +36,7 @@ public class MainWindow extends JFrame implements PropertyChangeListener {
@Override @Override
public void propertyChange(PropertyChangeEvent event) { public void propertyChange(PropertyChangeEvent event) {
if (event.getPropertyName().equals(Environment.TICK_PROPERTY_NAME)) { if (event.getPropertyName().equals(Environment.TURN_PROPERTY_NAME)) {
repaint(); repaint();
} }
} }

View File

@ -14,7 +14,7 @@ import java.util.Map;
public class ComponentIcon extends JComponent { public class ComponentIcon extends JComponent {
private static final Map<ComponentType, Image> loadedImages = new HashMap<>(); private static final Map<ComponentType, Image> loadedImages = new HashMap<>();
private static final int SPEED = 2; private static final int SPEED = 1;
private final Component component; private final Component component;
private final int destinationId; private final int destinationId;
@ -37,12 +37,14 @@ public class ComponentIcon extends JComponent {
@Override @Override
public void paintComponent(Graphics g) { public void paintComponent(Graphics g) {
position.translate(direction.x, direction.y);
g.translate(position.x, position.y); g.translate(position.x, position.y);
g.drawImage(image, 0, 0, null); g.drawImage(image, 0, 0, null);
} }
public void updatePosition() {
position.translate(direction.x, direction.y);
}
public boolean isAtDestination() { public boolean isAtDestination() {
return position.equals(destination); return position.equals(destination);
} }
@ -63,6 +65,8 @@ public class ComponentIcon extends JComponent {
} }
private static int normalizedDirection(int from, int to) { private static int normalizedDirection(int from, int to) {
// compare retourne 1, 0 ou -1 en comparant to à from.
// Il suffit de multiplier ce résultat par la vitesse désirée pour trouver la direction normalisée.
return Integer.compare(to, from) * SPEED; return Integer.compare(to, from) * SPEED;
} }

View File

@ -18,6 +18,9 @@ public class RouteLine extends JComponent {
int offsetFromOriginX = Math.min(from.x, to.x); int offsetFromOriginX = Math.min(from.x, to.x);
int offsetFromOriginY = Math.min(from.y, to.y); int offsetFromOriginY = Math.min(from.y, to.y);
// La position de la ligne dépend de x et y des bounds du JComponent.
// Ainsi, la position [0 0] est en fait [x y] des bounds.
// Il faut donc retirer ces positions pour ne pas dessiner en dehors du rectangle de dessin.
from.translate(-offsetFromOriginX, -offsetFromOriginY); from.translate(-offsetFromOriginX, -offsetFromOriginY);
to.translate(-offsetFromOriginX, -offsetFromOriginY); to.translate(-offsetFromOriginX, -offsetFromOriginY);
} }