¿Cómo implemento una tabla de despacho para un juego de aventuras de texto?

Estoy haciendo un aventura de texto en C#, y alguien me sugirió que usara un mesa de despacho en lugar de una declaración de cambio.

Aquí está el código de declaración de cambio:

        #region Public Methods
        public static void Do(string aString)
        {
                if(aString == "")
                        return;

                string verb = "";
                string noun = "";

                if (aString.IndexOf(" ") > 0)
                {
                        string[] temp = aString.Split(new char[] {' '}, 2);
                        verb = temp[0].ToLower();
                        noun = temp[1].ToLower();
                }
                else
                {
                        verb = aString.ToLower();
                }

                switch(Program.GameState)
                {
                        case Program.GameStates.Playing:
                                if (IsValidInput(Commands, verb, true))
                                {
                                        switch(verb) //this is the switch statement
                                        {
                                                case "help":
                                                case "?":
                                                        WriteCommands();
                                                        break;
                                                case "exit":
                                                case "quit":
                                                        Program.GameState = Program.GameStates.Quit;
                                                        break;
                                                case "move":
                                                case "go":
                                                        MoveTo(noun);
                                                        break;
                                                case "examine":
                                                        Examine(noun);
                                                        break;
                                                case "take":
                                                case "pickup":
                                                        Pickup(noun);
                                                        break;
                                                case "drop":
                                                case "place":
                                                        Place(noun);
                                                        break;
                                                case "use":
                                                        Use(noun);
                                                        break;
                                                case "items":
                                                case "inventory":
                                                case "inv":
                                                        DisplayInventory();
                                                        break;
                                                case "attack":
                                                        //attack command
                                                        break;
                                        }
                                }
                                break;

                        case Program.GameStates.Battle:
                                if(IsValidInput(BattleCommands, verb, true))
                                {
                                        switch(verb) //this is the other switch statement
                                        {
                                                case "attack":
                                                        //attack command
                                                        break;
                                                case "flee":
                                                case "escape":
                                                        //flee command
                                                        break;
                                                case "use":
                                                        //use command
                                                        break;
                                                case "items":
                                                case "inventory":
                                                case "inv":
                                                        //items command
                                                        break;
                                        }
                                }
                                break;
                }
        }
        #endregion

¿Cómo refactorizo ​​esto para usar una tabla de despacho?

preguntado el 03 de mayo de 12 a las 18:05

Deberías echar un vistazo esta pregunta, una de las respuestas muestra un ejemplo de C# que parece bastante correcto, en mi humilde opinión. -

Antes de preocuparme por eso, me centraría en escribir funciones más pequeñas y modulares ;-) -

@psycho Esto respondió perfectamente a mi pregunta. Deberías publicar esto como una respuesta para que pueda aceptarlo. -

@pst ¿Podría darme un ejemplo de qué arreglar? -

@ ryansworld10 Probablemente tendría cada una de las lógicas del estado del juego en su propia función para que las ramas (interruptores o despacho o lo que sea) no estén anidadas. -

3 Respuestas

La forma más sencilla sería usar un diccionario de delegados.

Por ejemplo:

Dictionary<string, Action> dispatch = new Dictionary<string, Action>();

dispatch["help"] = new Action(() => Console.WriteLine("Hello"));
dispatch["dosomething"] = new Action(() =>
{
    // Do something else
    Console.WriteLine("Do Something");
});

// Call the 'help' command
dispatch["help"]();

Para múltiples parámetros diferentes, podría ser más simple usar un Delegado base y usar una Invocación dinámica.

Dictionary<string, Delegate> dispatch = new Dictionary<string, Delegate>();

dispatch["help"] = new Action(() => Console.WriteLine("Hello"));
dispatch["dosomething"] = new Action<string>(s => Console.WriteLine(s));

dispatch["help"].DynamicInvoke();
dispatch["dosomething"].DynamicInvoke("World");

Y si usa .NET 4, también puede usar tipos dinámicos para resolver en tiempo de ejecución para reducir ligeramente el desorden de la invocación dinámica.

Dictionary<string, dynamic> dispatch = new Dictionary<string, dynamic>();

dispatch["help"] = new Action(() => Console.WriteLine("Hello"));
dispatch["dosomething"] = new Action<string>(s => Console.WriteLine(s));

dispatch["help"]();
dispatch["dosomething"]("World");

contestado el 03 de mayo de 12 a las 21:05

Esto ayuda, pero ¿cómo pasaría argumentos a las funciones llamadas en las acciones> - rshea0

@ryansworld10 Ver Action<T>, Action<T1,T2>, etc (ambos Action y Func vienen en muchos sabores parametrizados, que son tipos separados pero relacionados.) - usuario166390

Está bien, voy a ir con este. Parece ser la forma más sencilla de hacerlo. - rshea0

Todavía no entiendo muy bien cómo pasar argumentos a la declaración almacenada en el diccionario (para que pueda decirle al método llamado mi nombre). - rshea0

He añadido algunas variantes para ti. - tiránido

Tal vez se estaba refiriendo a 'Double Dispatch', o al Patrón de visitante.

Podría dividir su código para mejorar un estilo más 'despachador' de la siguiente manera:

public interface IGameState{
  void Help();
  void Question();
  void Attack();
}

public interface ICommand{
  bool IsValidFor(PlayingState state);
  bool IsValidFor(BattleState state);
  void Execute(IGameState state);
}

public class PlayingState : IGameState {
   public void Help(){ // Do Nothing }
   public void Question() { WriteCommands(); }

   private void WriteCommands(){ }
}

public class Battle : IGameState{
   public void Help(){ // Do Nothing }
   public void Question() { WriteCommands(); }
   public void Attack() { Roll(7); }

   private void Roll(int numDice){ }
}

public class CommandBuilder{
  public ICommand Parse(string verb){
    switch(verb){
       case "help":
         return new HelpCommand();
       case "?":
         return new QuestionCommand();
       case "attack":
         return new AttackCommand();
       default:
         return new UnknownCommand();
    }
  }
}

public class QuestionCommand(){
  bool IsValidFor(PlayingState  state){
     return true;
  }

  bool IsValidFor(BattleState state){
     return false;
  }

  void Execute(IGameState state){
     state.Question();
  }
}

public static void Do(string aString){
  var command = CommandBuilder.Parse(aString);
  if(command.IsValidFor(Program.GameStates))
     command.Execute(Program.Gamestates);
}

contestado el 03 de mayo de 12 a las 18:05

No tengo ni idea de lo que eso significa. Y esto no responde a mi pregunta de cómo implementarlo. - rshea0

Lo dudo. Una mesa de despacho es generalmente algo así como un Dictionary<string,Action>, al menos cuando hablo de ello. El patrón Visitor simplemente invierte todo por el bien de OO. - usuario166390

He actualizado mi respuesta con la forma en que interpretaría la implementación en un estilo de envío más doble. - Sheldon Warkentin

Bueno, es más un enfoque orientado a objetos. Con este patrón, mantiene su verbo cambiando todo en un solo lugar y, a medida que cambian sus interfaces, su código se romperá en el momento de la compilación; en lugar de tiempo de ejecución. - Sheldon Warkentin

Para retomar la introducción de @Jon Ericson:

Una tabla de despacho es una estructura de datos que asocia un valor de índice (o clave, lea el comentario de @pst a continuación) a una acción. Es un reemplazo bastante elegante para una declaración de tipo interruptor.

Con respecto a la parte de implementación, eche un vistazo a esta pregunta y particularmente a esta respuesta, que en mi humilde opinión parece bastante correcto pero sigue siendo fácil de entender.

contestado el 23 de mayo de 17 a las 13:05

Un "índice" podría no ser integral, como a menudo parece implicar. "clave" es un término más genérico, pero no se usa a menudo con arreglos que pueden hacer tablas de despacho adecuadas... - usuario166390

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.