Quelle est la meilleure façon de résoudre une explosion combinatoire d’interactions?

Une des choses sur laquelle je travaille actuellement présente certaines similitudes avec un jeu. Pour illustrer mon propos, je vais expliquer mon problème en utilisant un exemple tiré d’un jeu hypothétique et fictif.

Appelons cela DeathBlaster 4: The Deathening . Dans DB4, vous avez un certain nombre d’objects Ship qui rencontrent des Phenomena de façon périodique et aléatoire au cours de leur voyage. Un Phenomenon donné peut avoir zéro, un ou plusieurs Effects sur un Ship qui le rencontre. Par exemple, nous pourrions avoir quatre types de Ships et trois types de Phenomena .

                               Phénomènes
               =========================================
 Navires GravityWell BlackHole NebulaField
 ------------ ------------------------------------------ ----
 RedShip + 20% vitesse -50% puissance -50% bouclier
 BlueShip no effect invulnerable death Effets de divers
 GreenShip -20% de vitesse de mort + 50% de phénomène de bouclier sur les navires
 Décès YellowShip + 50% de puissance sans effet    

De plus, les Effects peuvent interagir les uns avec les autres. Par exemple, un GreenShip qui se trouve à la fois dans un GravityWell et un NebulaField peut NebulaField une sorte de synergie entre les SpeedEffect et ShieldEffect . Dans de tels cas, l’effet synergique est lui-même un Effect – par exemple, un effet PowerLevelSynergyEffect résulter de cette interaction. Aucune information autre que l’ensemble des Effects agissant sur un Ship n’est nécessaire pour résoudre le résultat final.

Vous pouvez commencer à voir un problème émerger ici. En tant que première approche naïve, chaque Ship devra savoir comment gérer chaque Phenomenon ou chaque Phenomenon devra connaître chaque Ship . Ceci est évidemment inacceptable, nous voudrions donc transférer ces responsabilités ailleurs. Clairement, il y a au moins une classe externe ici, peut-être un Mediator ou un Visitor de quelque sorte.

Mais quelle est la meilleure façon de le faire? La solution idéale aura probablement ces propriétés:

  • Il est aussi facile d’append un nouveau Ship que d’append un nouveau Phenomenon .
  • Les interactions qui ne produisent aucun effet sont les réactions par défaut et ne nécessitent pas de code supplémentaire à représenter. Convention sur la configuration.
  • Comprend comment les Effects interagissent les uns avec les autres et est capable de gérer ces interactions pour décider du résultat final.

Je pense que j’ai déjà décidé de mon approche, mais j’aimerais savoir quel est le meilleur consensus. Où commencerais-tu? Quelles pistes exploreriez-vous?



Mise à jour de suivi: Merci pour vos réponses, tout le monde. Voici ce que j’ai fini par faire. Ma principale observation était que le nombre d’ Effects différents semblait faible par rapport au nombre d’interactions possibles Phenomena × Ships . En d’autres termes, bien qu’il existe de nombreuses combinaisons possibles d’interactions, le nombre de types de résultats de ces interactions est plus petit.

Vous pouvez voir que, par exemple, bien qu’il y ait 12 combinaisons d’interactions dans le tableau, il n’y a que cinq types d’effets: modifications de la vitesse, modifications du pouvoir, modifications du bouclier, invulnérabilité, mort.

J’ai introduit une troisième classe, InteractionResolver , pour déterminer le résultat des interactions. Il contient un dictionnaire qui mappe les paires Ship-Phenomenon à des Effects (qui sont essentiellement un délégué qui exécute l’effet et certaines métadonnées). Chaque Ship reçoit un EffectStack correspondant aux Effects qu’il subit lorsque le résultat du calcul de l’interaction est terminé.

Ships utilisent ensuite EffectStack pour déterminer le résultat réel des Effects sur eux, en ajoutant des modificateurs à leurs atsortingbuts et propriétés existants.

J’aime ça parce que:

  1. Les navires n’ont jamais besoin de savoir sur Phenomena.
    • La complexité de la relation navire-phénomène est résumée dans InteractionResolver.
    • InteractionResolver résume les détails de la résolution des effets multiples, voire complexes. Les navires doivent uniquement appliquer les effets nécessaires.
    • Cela permet d’append des refactorisations utiles. Par exemple, la manière dont un navire traite les effets peut être différenciée en créant une EffectProcessorStrategy . La valeur par défaut peut être de traiter tous les effets, mais, par exemple, une BossShip peut ignorer des effets mineurs en ayant une autre EffectProcessorStrategy .

Une option potentielle intéressante consisterait à utiliser une variante du modèle de visiteur .

Judith Bishop et R. Nigel Horspool ont rédigé un article sur l’ efficacité des modèles de conception, dans lequel ils ont expliqué diverses variantes du modèle de visiteur classique à l’aide de fonctions C # 3.

Je voudrais en particulier voir comment ils travaillent avec les delegates pour gérer le modèle de visiteurs. L’utilisation d’une liste ou d’une stack de delegates peut vous donner un moyen intéressant de gérer plusieurs effets provenant de plusieurs objects et d’être beaucoup plus facile à étendre de part et d’autre de la hiérarchie des classes (append des navires ou append des effets) sans modifications majeures du code.

Pas tout à fait une réponse, mais pour moi cela crie assez “modèle de propriétés”. Il y a un discours de yegge bien connu à ce sujet, je pense que cela vous offrira quelques conseils décents.

http://steve-yegge.blogspot.com/2008/10/universal-design-pattern.html

Cela ressemble au dilemme classique polymorphism / visiteur de la POO. Cependant, vos exigences facilitent les choses.

Fondamentalement, je créerais un Ship classe de base à partir Ship tous les navires en béton dérivent. Cette classe aurait des méthodes:

 class Ship { void encounterBlackHole() {} void encounterNebula() {} ... etc. ... }; 

avec des corps vides par défaut. Lorsque vous ajoutez un nouveau phénomène, vous ajoutez simplement une nouvelle méthode avec un corps vide. (Les méthodes peuvent avoir des arguments, comme les coordonnées ou le poids du trou noir, etc.)

Pour les effets et leur interaction – je pense que vous devriez append plus d’informations sur la façon dont vous le souhaitez, par exemple. les interactions sont-elles rares ou habituelles, sont-elles cumulatives d’une certaine manière, sont-elles confinées à un ensemble limité de variables qu’elles contrôlent …

Cela ressemble à un problème classique d’envoi multiple pour moi.

Question interessante

D’une manière ou d’une autre, un navire devra savoir quels phénomènes peuvent l’affecter et quels phénomènes il influe sur quel navire.

Cela pourrait être stocké dans un fichier XML analysé au moment de l’exécution.

Vous pourriez peut-être utiliser le motif Décorateur pour calculer les effets. Vous générez divers phénomènes au moment de l’exécution.

Supposons que vos navires implémentent une interface IShip et que, partout dans votre code, vous utilisiez IShips.

Maintenant, supposons que tous vos phénomènes implémentent également l’interface IShip (requirejse par le motif de conception du décorateur).

 IShip myShip = myShip.AddPhenomena(PhenomenaGeneratedByAFactoryForThisShip); 

Dans les phénomènes, vous encapsulez les méthodes du vaisseau original, de sorte que vous puissiez modifier les propriétés.

En outre, si vous utilisez le modèle de stratégie, vous pouvez générer tout type de phénomène souhaité.

Supprimer un phénomène peut être utilisé en marchant le tas de navires décorés que vous avez et en le remballant, donc je ne vois aucun problème là.

En ce qui concerne les synergies, je pense que j’utiliserais un phénomène légèrement modifié, qui ne fonctionne que si le navire cible a tous les phénomènes sur lui-même.

soit chaque navire devra savoir comment gérer chaque phénomène, ou chaque phénomène devra connaître chaque navire.

Je pense que c’est la clé de votre problème et que c’est vrai si chaque interaction navire-phénomène est unique. Dans la table que vous avez créée, cela semble être le cas. Pour n navires et m phénomènes, vous devez spécifier n * m interaction. Vous avez raison de sentir un problème de maintenance.

Le seul moyen de sortir est de rendre les interactions navire-phénomène moins uniques en faisant dépendre l’effet des phénomènes sur les propriétés du navire. Par exemple, nous pourrions dire que seuls les navires construits avec une étrange alumine survivront à un trou noir.

Mais le fait d’avoir une seule propriété ne vous rapporte rien, vous auriez tout aussi bien pu spécifier quels navires sont affectés par des trous noirs. La clé est que plusieurs propriétés présentent la même explosion combinatoire.

Ainsi, les navires construits avec de l’alumine étrange survivront aux trous noirs, et les navires construits sans peuvent aller plus vite. 1 propriété vous permet de spécifier 2 choses (1 bit = 2 possibilités). Les navires construits avec des moteurs à corbeaux iront plus vite dans une zone de chaîne. Les navires dotés à la fois de moteurs à corbeaux et d’étranges alumines gagneront un bouclier de 50% dans un champ de nébuleuse, etc.

L’ajout de propriétés à des navires vous permet d’éviter de spécifier la manière dont chaque phénomène interagit avec chaque navire, tout en ayant toujours un comportement et un comportement appropriés de chaque navire.

S’il y a M navires, il vous suffit alors des propriétés log2 (M) pour donner à chaque navire un comportement unique.

Je pense que la réponse à un problème dépend de la qualité de la question posée.

Je pense que la manière de concevoir dépend de la nature de la question (ou la question sera posée à l’avenir)

vous donnez une table, alors je pense que la solution consiste à maintenir une table et à l’interroger.

code python ici: (non testé et ne montre que pour un exemple)

 class Ship(): def __init__(self,type): self.type=type def encounterPhenomena(self,type): # let Phenomena to process ship p = Phenomena(type) p.process(self) class Phenomena(): processTable = {} def __init__(self,type): self.type=type def process(self,ship): try: self.processTable[self.type](ship.type) #query the internal table to process ship except: pass #if Phenomena don't know this ship type then no process.. def addType(type,processMethod): processTable[type]=processMethod #add new Phenomena, and add processMethod def run(): A = Ship(type = 'RedShip') A.encounterPhenomena(type='GravityWell'); 

Si la méthode de traitement a changé, modifiez simplement la méthode de traitement dans la classe Phenomena.

Si vous pensez que le navire a besoin de savoir comment traiter les phénomènes, changez la méthode de traitement en classe de navire.

ou vous pensez qu’il y a d’autres choses non seulement Phenomena doit changer le statut du vaisseau (comme les autres navires, shoal rock), vous devez maintenir une table de processus dans la classe des vaisseaux et en faire un des Phénomènes,

Encore une fois, comment concevoir dépend de la question elle-même.