Comment mettre en œuvre un circuit ou un système d'alimentation (comme Redstone dans Minecraft)

8

Je veux implémenter un système d'alimentation comme le système Redstone dans Minecraft.

J'ai n sources d'alimentation et m câbles. Si je déconnecte la source d'alimentation ou un câble, le circuit doit s'éteindre. modèle de système de câbles

Comment éviter les cercles? Si chaque câble avec le statut "on" alimente les câbles à proximité, je peux créer des cercles infinis où aucune source d'alimentation n'est impliquée (voir image). Plus le site est qu'il fonctionne en T = m

Je pouvais envoyer une rafale de puissance à travers le graphique à partir de chaque source d'alimentation et à chaque appel de mise à jour, j'éteins chaque câble. Le problème est qu'il s'exécute en T = n * m.

Existe-t-il une meilleure pratique? Dans Minecraft, le système Redstone était très lent, donc je pense que j'ai oublié quelque chose.

EDIT: Le système devrait fonctionner sans décroissance basée sur la distance.

Benedikt S. Vogler
la source
Cela dépend du modèle que vous essayez d'implémenter. Par exemple, une source d'alimentation pourrait fournir x unités d'énergie consommées lors de l'utilisation. Un autre modèle est que votre source d'alimentation a un potentiel qui limite la charge que vous pouvez placer sur le circuit qui fonctionne tant que vous alimentez votre source d'alimentation.
user3730788

Réponses:

7

Propagation récursive. Par exemple, vous avez une lampe connectée par N objets de câble à une batterie. La lampe demande au Nième câble s'il est alimenté (c'est le câble relié directement à la lampe). Le Nième câble demande alors au câble N-1 s'il est alimenté et ainsi de suite. Chaque fois qu'un objet est demandé s'il est alimenté ou non, il définit unlastEvaluated variable sur la durée d'image actuelle. La récursivité touche un nœud d'extrémité, comme une batterie, ou lorsqu'elle atteint un objet qui a déjà été évalué dans ce cadre (cela évite une récursion infinie). Ces propagations ne se produisent que lorsque le système change. Les changements incluent l'ajout / la suppression de pièces ou de commutateurs basculés.

Il n'y a pas de décroissance de distance ou de contraintes similaires avec ce système. Je l'ai utilisé pour créer un simulateur de porte logique et cela fonctionne pour divers exemples logiques comme une bascule.

MichaelHouse
la source
Cette. De plus, j'ajouterais une référence à la source d'alimentation. Si un élément est modifié, dites à la source référencée de mettre à jour le réseau qu'il alimente. De cette façon, vous devez non seulement mettre à jour lorsque quelque chose change, vous savez également quels éléments sont affectés. Les changements dans votre réseau peuvent également être identifiés facilement: un changement nécessiterait-il une réévaluation ou non, etc.
Felsir
@Felsir Vous pourriez le faire, mais je ne le recommanderais pas. Il augmente la complexité sans trop de gain. Il peut y avoir plusieurs sources d'alimentation et plusieurs puits par source. Il serait plus facile de simplement faire confiance à l'évaluation, et ce n'est vraiment pas très gourmand en ressources.
MichaelHouse
La solution manque d'une chose, je pense: si vous avez un câble au milieu et que l'un est alimenté et que l'autre extrémité n'en a pas un seul obtiendra le changement. Vous avez essentiellement créé un arbre avec la variable "lastEvauluated" en supprimant les cercles. Maintenant, le changement se propage vers le haut dans l'arborescence mais pas vers le bas. Je vais l'essayer dans mon implémentation en poussant les modifications vers le bas après les avoir relevées.
Benedikt S. Vogler
J'ai ajouté la solution à votre réponse en une seule modification car mon ajout à l'algorithme fonctionne.
Benedikt S. Vogler
1
@Benedikt Vous décrivez le comportement attendu. C'est parce que mon système utilise des entrées et des sorties. Les portes ET et les portes OU ne sont pas bidirectionnelles (elles utilisent l'implémentation de diode). Donc, si je voulais que la puissance se déplace vers quelque chose au-delà du nœud B, je dirigerais une sortie de cette façon. Je vous suggère de créer une nouvelle réponse pour décrire votre système bidirectionnel.
MichaelHouse
2

Dans minecraft, il y a une décroissance basée sur la distance avec une distance de décroissance très courte (plage de 16 blocs).

Ce dont vous avez besoin un test de connectivité entre les graphiques .

Une façon de le faire serait de prendre à plusieurs reprises chaque bord et de combiner les nœuds connectés et en un seul nœud. Une fois que tous les bords ont disparu, vous vous retrouverez avec un nœud pour chaque réseau. L'envoi de puissance est alors insignifiant.

monstre à cliquet
la source
Enregistrer un sous-graphique dans un nœud semble très intelligent, mais je dois penser un peu à une implémentation concrète car cette réponse semble un peu vague en ne mentionnant que le "test de connectivité entre les graphiques" qui semble être une partie assez importante de cette solution.
Benedikt S. Vogler
en fait, cette description est une variante de l'algorithme de Karger pour trouver des coupes min. Mais à la place, vous continuez jusqu'à ce qu'il n'y ait plus de bords à contracter.
ratchet freak
1

Un bloc alimenté a plusieurs connexions d'entrée / sortie, mais à un point de départ, nous ne savons pas quand il est en entrée ou en sortie.

Chaque bloc a une "tension" qui est l'énergie qui y arrive moins la perte / l'utilisation.

Un bloc alimenté alimentera tous les blocs environnants, et chaque bloc prendra en entrée la tension la plus élevée des blocs environnants. Vous pouvez également compliquer le système en définissant une intensité, mais je ne resterai avec Voltage que pour plus de simplicité.

Chaque fois qu'un changement est effectué sur le circuit, en ajoutant / supprimant des blocs, ou par le circuit lui-même, le changement doit être propagé à tout le circuit jusqu'à la stabilité.

Je vous suggère de concevoir une interface pour tout objet propulsé (cube dans MC):

class PowerInterface
{
protected:
    std::vector<shared_ptr<PowerInterface>> sibling;

    double energy=0;
    bool   isActive = false;

    virtual void propagate(double inEnergy) = 0;

    virtual void addSibling(shared_ptr<PowerInterface> newSibling) = 0;
    virtual void removeSibling( shared_ptr<PowerInterface> remSibling) =0;
};

Supposons donc que vous implémentiez addSibling et removeSibling, la partie la plus importante est la fonction de propagation:

void PoweredCube::propagate( double inEnergy ) 
{
    // Define the behaviour
    energy = inEnergy-1.0; // Normal device
    energy = inEnergy-0.1; // Normal cable
    energy = 10.0;         // Normal source of power.

    if (energy<0.0)
    { 
        energy = 0.0;
        isActive = false;
        // No energy, so do not propagate anymore
        return;
    }
    isActive = true;

    // Propagate
    for (auto &s: sibling)
    {
        // Only propagate to sibling with less energy. 
        if (energy > s->energy) s->propagate( energy);
    }
}

En tant que solution récursive, chaque bloc devrait réduire un peu l'énergie, ne jamais l'augmenter. La source d'énergie peut définir une valeur fixe, mais ne jamais augmenter en fonction des entrées. Cela ne devrait pas être un problème car tous les "vrais" systèmes fonctionnent de cette façon.

Adrian Maire
la source
Mais avec cette solution, chaque boucle a besoin d'appels "n = 1 / diminution" avant d'être arrêtée. N'est-ce pas un peu cher?
Benedikt S. Vogler
Non, vous ne mettez à jour que les modifications: lorsque vous créez / supprimez / modifiez un bloc, ou lorsqu'un bloc produit une modification active. Si vous regardez le code, la plupart des modifications ne propageront que quelques blocs. Bien sûr, vous pourriez simplifier le graphique comme le dit @ BenediktS.Vogler, mais à mon humble avis, ce sera déjà assez rapide. Supposons 1000 blocs actifs dans la zone active, ce qui est déjà un énorme mécanisme. Même dans le pire des cas, une mise à jour complète ne représente que quelques opérations * 1000, ce qui est court. Habituellement, seuls quelques blocs sont mis à jour
Adrian Maire