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() {
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++) {
Node node = nodes.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) return true;
if (isElementNode(node)) return true;
}
return false;
@ -35,7 +35,11 @@ public class ElementListIterator implements Iterator<Element> {
Node node = nodes.item(position);
position++;
if (node.getNodeType() == Node.ELEMENT_NODE) return (Element) node;
if (isElementNode(node)) return (Element) node;
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 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 int getId() {
@ -41,8 +46,4 @@ public abstract class Building {
public BuildingState getState() {
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 Map<ComponentType, Integer> inputsCount = new HashMap<>();
private float productionInterval;
private boolean isProductionStarted;
private boolean warehouseFull;
private long productionTicks = 0L;
private boolean haltProduction;
private int productionTicks = 0;
public Factory(int id, String type, int x, int y, FactoryMetadata metadata) {
super(id, type, x, y);
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;
if (isProductionStarted) {
productionTicks = metadata.getProductionInterval();
productionTicks = (int) productionInterval;
}
}
@Override
public Optional<Component> update() {
if (!isProductionStarted || (productionTicks == 0 && warehouseFull)) return Optional.empty();
public Optional<Component> produce() {
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++;
if (productionTicks < metadata.getProductionInterval()) {
if (productionTicks < productionInterval) {
// La production n'est pas finie
updateState();
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) {
isProductionStarted = false;
}
@ -50,10 +59,13 @@ public class Factory extends Building implements WarehouseObserver {
@Override
public void processInput(Component input) {
int inputCount = 0;
// S'assure qu'une entrée pour ce type de composant existe.
if (inputsCount.containsKey(input.getType())) {
inputCount = inputsCount.get(input.getType());
}
// Augmente la quantité de ce type de composant en stock.
inputsCount.put(input.getType(), inputCount + 1);
if (hasEnoughComponents()) {
@ -67,12 +79,33 @@ public class Factory extends Building implements WarehouseObserver {
}
@Override
public void updateWarehouseState(boolean full) {
warehouseFull = full;
public void updateWarehouseState(BuildingState state) {
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() {
float productionRatio = productionTicks / (float) metadata.getProductionInterval();
float productionRatio = productionTicks / productionInterval;
if (productionRatio >= (2 / 3f)) {
state = BuildingState.FULL;
} else if (productionRatio >= (1 / 3f)) {
@ -84,7 +117,8 @@ public class Factory extends Building implements WarehouseObserver {
private boolean hasEnoughComponents() {
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;
}
}
@ -93,6 +127,7 @@ public class Factory extends Building implements WarehouseObserver {
}
private void startProduction() {
// Retire les composants nécessaires des entrées
for (FactoryInput input : metadata.getInputs()) {
int inputCount = inputsCount.get(input.getType());
inputsCount.put(input.getType(), inputCount - input.getQuantity());

View File

@ -3,7 +3,7 @@ package simulation;
import java.util.Random;
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();

View File

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

View File

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

View File

@ -4,7 +4,7 @@ public interface WarehouseObserver {
/**
* 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 {
void attach(WarehouseObserver observer);
void detach(WarehouseObserver observer);
void dettach(WarehouseObserver observer);
void notifyObservers();
}

View File

@ -4,7 +4,7 @@ import javax.swing.*;
public class Environment extends SwingWorker<Object, String> {
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 long tick = 0L;
@ -14,7 +14,7 @@ public class Environment extends SwingWorker<Object, String> {
while (active) {
Thread.sleep(DELAY);
firePropertyChange(TICK_PROPERTY_NAME, tick, ++tick);
firePropertyChange(TURN_PROPERTY_NAME, tick, ++tick);
}
return null;
}

View File

@ -33,8 +33,11 @@ public class MainPanel extends JLayeredPane {
simulationData = configuration.getSimulationData();
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()) {
BuildingIcon icon = this.initializeBuilding(building);
routesOffset = icon.getWidth() / 2;
@ -72,27 +75,37 @@ public class MainPanel extends JLayeredPane {
if (configuration == null) return;
updateBuildings();
updateComponentIcons();
}
private void updateComponentIcons() {
Collection<ComponentIcon> removedIcons = new ArrayList<>();
for (ComponentIcon icon : componentIcons) {
if (icon.isAtDestination()) {
removedIcons.add(icon);
}
componentIcons.forEach(i -> {
i.updatePosition();
boolean destinationReached = i.isAtDestination();
if (destinationReached) {
removedIcons.add(i);
}
});
removedIcons.forEach(i -> {
componentIcons.remove(i);
remove(i);
Building outputBuilding = getBuildingById(i.getDestinationId());
outputBuilding.processInput(i.getComponent());
pushComponentToBuilding(i.getComponent(), i.getDestinationId());
});
}
private void pushComponentToBuilding(Component c, int buildingId) {
Building outputBuilding = getBuildingById(buildingId);
outputBuilding.processInput(c);
}
private void updateBuildings() {
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
public void propertyChange(PropertyChangeEvent event) {
if (event.getPropertyName().equals(Environment.TICK_PROPERTY_NAME)) {
if (event.getPropertyName().equals(Environment.TURN_PROPERTY_NAME)) {
repaint();
}
}

View File

@ -14,7 +14,7 @@ import java.util.Map;
public class ComponentIcon extends JComponent {
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 int destinationId;
@ -37,12 +37,14 @@ public class ComponentIcon extends JComponent {
@Override
public void paintComponent(Graphics g) {
position.translate(direction.x, direction.y);
g.translate(position.x, position.y);
g.drawImage(image, 0, 0, null);
}
public void updatePosition() {
position.translate(direction.x, direction.y);
}
public boolean isAtDestination() {
return position.equals(destination);
}
@ -63,6 +65,8 @@ public class ComponentIcon extends JComponent {
}
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;
}

View File

@ -18,6 +18,9 @@ public class RouteLine extends JComponent {
int offsetFromOriginX = Math.min(from.x, to.x);
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);
to.translate(-offsetFromOriginX, -offsetFromOriginY);
}