Enseñando a un viejo AI Nuevos trucos de magia -- java campo con game campo con ai campo con libgdx camp codereview Relacionados El problema

Teaching an Old AI New Magic Tricks


5
vote

problema

Español

Recientemente, quería enseñar al AI de mi juego de castillo cómo usar los hechizos que he agregado al juego. Puedes probar el juego aquí: CASTLEPARTS

Inicialmente pensé que tendría que agregar subclases para cada una de las cuatro razas jugables para tener en cuenta los diferentes hechizos que cada uno tiene disponible. Sin embargo, terminé subiendo con un enfoque más genérico que funciona tan bien.

Estoy usando el GDX-AI Biblioteca para la AI. Inicialmente (antes de que se agreguen los hechizos), la lógica de AI Core se veía así:

  @Override public void update(AIPlayerConquer entity) {     //this is the hierarchy of action priorities     //when idle, this will be called to search for what state to select next      //shoot ogres first because they will destroy walls     entity.findPersons();     if (entity.shouldAttackPersons()) {         entity.stateMachine.changeState(SHOOT_PERSONS);     }      //shoot if the opponent is aggressive or controls a lot of the map     else if (entity.isOpponentAggressive() ||              entity.shouldAttackBasedOnOpponentPercentage() ||              entity.shouldAttackBasedOnPercentOwned()) {          entity.stateMachine.changeState(SHOOT);      //otherwise try to build     } else {         entity.stateMachine.changeState(BUILD);     } }   

Con solo algunos cambios pequeños, agregué hechizos a la lógica central. Quería poner hechizos a una prioridad más alta que las acciones regulares porque hacen que el juego sea más emocionante. Aquí está toda la clase:

  make_unique0  

Cada AI solo tiene 3 hechizos disponibles. La siguiente tarea fue dividirlos en categorías para que se pueda aplicar alguna lógica general para decidir cuándo usarlas. Primero creé un tipo para describir el objetivo de un hechizo:

  make_unique1  

Luego agregué un nuevo campo al make_unique2 Enum y le dio a cada tipo de hechizo un destino. A continuación, dividí los hechizos en hechizos agresivos y defensivos agregando esto al make_unique3 enum.

  make_unique4  

Cuando se inicializa la clase make_unique5 , los hechizos disponibles se agregan a las listas locales para que sepan qué hechizos tienen:

  make_unique6  

Se utiliza algún código simple para determinar si la AI puede o no o no, debe intentar lanzar un hechizo:

  make_unique7  

y, finalmente, este código se llama cuando el AI realmente lanza el hechizo:

  make_unique8  

Creo que la lógica es bastante clara aquí, pero estoy buscando otros enfoques que puedan ser menos verbosos, y, como siempre, quiero escuchar sobre cualquier forma en que se pueda mejorar el código. ¡Gracias!

Además, aquí hay una captura de pantalla de un Hechote de fundición AI en otro:

CastleParts deletrea

Original en ingles

Recently I wanted to teach the AI of my castle game how to use the spells that I have added to the game. You can try out the game here: Castleparts

Initially I thought that I would have to add subclasses for each of the four playable races in order to account for the different spells that each one has available. However I ended up coming up with a more generic approach that works just as well.

I'm using the gdx-ai library for the AI. Initially (before spells added) the core AI logic looked like this:

@Override public void update(AIPlayerConquer entity) {     //this is the hierarchy of action priorities     //when idle, this will be called to search for what state to select next      //shoot ogres first because they will destroy walls     entity.findPersons();     if (entity.shouldAttackPersons()) {         entity.stateMachine.changeState(SHOOT_PERSONS);     }      //shoot if the opponent is aggressive or controls a lot of the map     else if (entity.isOpponentAggressive() ||              entity.shouldAttackBasedOnOpponentPercentage() ||              entity.shouldAttackBasedOnPercentOwned()) {          entity.stateMachine.changeState(SHOOT);      //otherwise try to build     } else {         entity.stateMachine.changeState(BUILD);     } } 

With just some small changes, I added spells to the core logic. I wanted to put spells at a higher priority than regular actions because they make the gameplay more exciting. Here's the entire class:

public enum AIPlayerConquerState implements State<AIPlayerConquer> {     IDLE,      SHOOT {         @Override         public void enter(AIPlayerConquer entity) {              entity.findOpponentWallTiles();              if (!entity.doesOpponentHaveWalls()) {                 entity.stateMachine.changeState(BUILD);             } else {                 entity.angryAsEnemy();                 entity.tryToShootWalls();             }              entity.stateMachine.changeState(IDLE);         }     },      SHOOT_PERSONS {         @Override         public void enter(AIPlayerConquer entity) {              entity.findPersons();              if (!entity.shouldAttackPersons()) {                 entity.stateMachine.changeState(BUILD);             } else {                 entity.tryToShootPersons();             }              entity.stateMachine.changeState(IDLE);         }     },      BUILD {         @Override         public void enter(AIPlayerConquer entity) {             entity.buildingAsEnemy();             entity.doActionForConquerGame();              entity.stateMachine.changeState(IDLE);         }     },      AGGRESIVE_SPELL {         @Override         public void enter(AIPlayerConquer entity) {             entity.tryToCastOffensiveSpell();              entity.stateMachine.changeState(IDLE);         }     },      DEFENSIVE_SPELL {         @Override         public void enter(AIPlayerConquer entity) {             entity.tryToCastDefensiveSpell();              entity.stateMachine.changeState(IDLE);         }     };      @Override     public void update(AIPlayerConquer entity) {         //this is the hierarchy of action priorities         //when idle, this will be called to search for what state to select next          //shoot ogres first because they will destroy walls         entity.findPersons();         if (entity.shouldAttackPersons()) {             entity.stateMachine.changeState(SHOOT_PERSONS);         }          //shoot if the opponent is aggressive or controls a lot of the map         else if (entity.isOpponentAggressive() ||                  entity.shouldAttackBasedOnOpponentPercentage() ||                  entity.shouldAttackBasedOnPercentOwned()) {              if (entity.shouldCastSpell(true)) {                 entity.stateMachine.changeState(AGGRESIVE_SPELL);             } else {                 entity.stateMachine.changeState(SHOOT);             }          //otherwise try to build         } else {             if (entity.shouldCastSpell(false)) {                 entity.stateMachine.changeState(DEFENSIVE_SPELL);             } else {                 entity.stateMachine.changeState(BUILD);             }         }     }      @Override     public void enter(AIPlayerConquer entity) {     }     @Override     public void exit(AIPlayerConquer entity) {     }     @Override     public boolean onMessage(AIPlayerConquer entity, Telegram telegram) {         return false;     } } 

Each AI only has 3 spells available. The next task was to split them up into categories so that some general logic could be applied to decide when to use them. First I created a type to describe the target of a spell:

public enum SpellTarget {      MY_CRYSTALS,     MY_WALLS,     MY_EMPTY_SPACE,     MY_CANNON,     OPPONENT_WALLS,     OPPONENT_EMPTY_SPACE,     OPPONENT_CANNON;  } 

Then I added a new field to the SpellType enum and gave each spell type a target. Next I split the spells into aggressive and defensive spells by adding this to the SpellType enum.

public final static List<SpellType> aggresiveSpells = SpellType.getAggresiveSpells(); public final static List<SpellType> defensiveSpells = SpellType.getDefensiveSpells();  private static List<SpellType> getAggresiveSpells() {     List<SpellType> types = new ArrayList<SpellType>();     types.add(FIREBALL);     types.add(FIRE_WALL);     types.add(SKELETONS);     types.add(OGRES);     types.add(LIGHTNING);     types.add(CANNON_CHARGE);     types.add(ENERGIZE_CRYSTALS);     return types; }  private static List<SpellType> getDefensiveSpells() {     List<SpellType> types = new ArrayList<SpellType>();     types.add(SHIELD);     types.add(STATIC_CHARGE);     types.add(DIG);     types.add(SHROUD);     types.add(BONUS_WALLS);     return types; } 

When the AIPlayer class is initialized, the available spells are added to local lists so that they know which spells they have:

for (SpellType type : playerType.spells) {     if (SpellType.aggresiveSpells.contains(type)) {         this.aggresiveSpells.add(type);     } else if (SpellType.defensiveSpells.contains(type)) {         this.defensiveSpells.add(type);     } } 

Some simple code is used to determine whether or not the AI can or should try to cast a spell:

protected boolean shouldCastSpell(boolean offensive) {     if (this.timeSinceSpellCast < this.difficulty.minSecondsBetweenSpells) {         return false;     }      if (offensive) {         return this.canAffordAggressiveSpell();     } else {         return this.canAffordDefensiveSpell();     } }  protected boolean canAffordAggressiveSpell() {     if (this.aggresiveSpells.size() == 0) {         return false;     }      this.spellsReadyToCast = this.getAffordableSpells(this.aggresiveSpells, this.spellsReadyToCast);     return this.spellsReadyToCast.size() > 0; }  protected boolean canAffordDefensiveSpell() {     if (this.defensiveSpells.size() == 0) {         return false;     }      this.spellsReadyToCast = this.getAffordableSpells(this.defensiveSpells, this.spellsReadyToCast);     return this.spellsReadyToCast.size() > 0; }  protected List<SpellType> getAffordableSpells(List<SpellType> availableSpells, List<SpellType> canAfford) {     canAfford.clear();     for (SpellType type : availableSpells) {         if (this.hasEnergyForSpell(type)) {             canAfford.add(type);         }     }     return canAfford; } 

And finally, this code is called when the AI actually casts the spell:

public void tryToCastOffensiveSpell() {     this.spellsReadyToCast = this.getAffordableSpells(this.aggresiveSpells, this.spellsReadyToCast);     if (this.spellsReadyToCast.size() == 0) {         return;     }     this.tryToCastSpell(this.spellsReadyToCast); }  public void tryToCastDefensiveSpell() {     this.spellsReadyToCast = this.getAffordableSpells(this.defensiveSpells, this.spellsReadyToCast);     if (this.spellsReadyToCast.size() == 0) {         return;     }     this.tryToCastSpell(this.spellsReadyToCast); }  public void tryToCastSpell(List<SpellType> spells) {     this.timeSinceSpellCast = 0;      int randomIndex = this.random.nextInt(spells.size());     SpellType randomSpell = spells.get(randomIndex);      switch(randomSpell.target) {     case MY_CANNON:         this.castSpellOnMyCannon(randomSpell);         break;     case MY_CRYSTALS:         this.castSpellOnMyCrystals(randomSpell);         break;     case MY_EMPTY_SPACE:         this.castSpellOnMyEmptySpace(randomSpell);         break;     case MY_WALLS:         this.castSpellOnMyWalls(randomSpell);         break;     case OPPONENT_CANNON:         this.castSpellOnOpponentCannon(randomSpell);         break;     case OPPONENT_EMPTY_SPACE:         this.castSpellOnOpponentEmpty(randomSpell);         break;     case OPPONENT_WALLS:         this.castSpellOnOpponentWalls(randomSpell);         break;     } } 

I think that the logic is fairly clear here, but I'm looking for other approaches that might be less verbose, and as always I want to hear about any ways the code can be improved. Thanks!

Also, here is a screenshot of one AI casting spells on another one:

Castleparts Spells

           

Lista de respuestas

2
 
vote
vote
La mejor respuesta
 

if (isdigit(guess + 48)) {7 es bastante inusual: los enumeres no están destinados a mantener la lógica "Negocio", pero parece que se hace cumplir la API usada.

General: Prefiero if (isdigit(guess + 48)) {8 sobre sus especializaciones (lista, set) en la API pública; Le brinda más flexibilidad para elegir / cambiar las implementaciones (ver más abajo).

if (isdigit(guess + 48)) {9 : 1) Faltan constantes en el post. 2) Las colecciones serán mucho más eficientes (y más seguras) cuando se definen como:

  00  

Se puede simplificar la inicialización de colecciones de colecciones, ya que (eliminando ambos bucles):

  01  

"Algunos código simple" se pueden simplificar (y acelerar) además:

  02  

La parte final se puede simplificar también:

  03  

Buena suerte con hechizos de fundición :)

 

AIPlayerConquerState is quite unusual - enums are not meant to hold "business" logic but it seems its enforced by used API.

General: prefer Collection over its specializations (List, Set) in public API; it gives you more flexibility for choosing/changing implementations (see below).

SpellType: 1) constants are missing in the post 2) collections will be much more efficient (and safer) when defined as:

public final static Collection<SpellType> defensiveSpells = Collections.unmodifiableSet(         EnumSet.of( SHIELD, STATIC_CHARGE, DIG, SHROUD, BONUS_WALLS ) ); public final static Collection<SpellType> aggresiveSpells         = Collections.unmodifiableSet( EnumSet.complementOf(defensiveSpells) ); 

Spells collections initialization could be then simplified as (removing both loops):

aggresiveSpells = EnumSet.copyOf( playerType.spells ).retainAll( SpellType.aggresiveSpells ); defensiveSpells = EnumSet.copyOf( playerType.spells ).retainAll( SpellType.defensiveSpells ); 

"some simple code" can be simplified (and speed up) further:

protected boolean shouldCastSpell(boolean offensive) {     if (timeSinceSpellCast < difficulty.minSecondsBetweenSpells) {         return false;     }     return offensive ? hasAffordableSpells(aggresiveSpells) : hasAffordableSpells(defensiveSpells); }  protected boolean hasAffordableSpells(Collection<SpellType> availableSpells) {     return availableSpells.stream().anyMatch( s -> hasEnergyForSpell(s) ); } 

The final part can be simplified as well:

public void tryToCastOffensiveSpell() {     List<SpellType> affordableSpells = aggresiveSpells.stream()             .filter( s -> hasEnergyForSpell(s) )             .collect( Collectors.toList() );     if (!affordableSpells.isEmpty()) { tryToCastSpell(affordableSpells); } } 

Good luck with casting spells :)

 
 

Relacionados problema

18  Generación de imágenes "Spot Spot"  ( Heat spot image generation ) 
Escribí una función que genera una imagen como: La función es la siguiente: /** Draws a texture wherein are spots of "heat" where a pixel's spotCo...

5  Dibujando un sistema de partículas a base de cuadrícula  ( Drawing a grid based particle system ) 
Durante algún tiempo, he estado trabajando en un juego que es superficialmente similar en apariencia en este . Un mundo está lleno de partículas que se mueve...

26  Convierta la fuente de mapa de bits a la textura Atlas  ( Convert bitmap font to texture atlas ) 
Quería hacer que las texturas que comprenden un glifo de fuente de mapa de bits en la pantalla directamente como un 99887776655544330 en libgdx. Cuando real...

3  Actualizando archivos de la biblioteca de marcos de juegos libgdx  ( Updating libgdx game framework library files ) 
Escribí un script que actualiza algunos archivos de la biblioteca para The Game Framework libgdx agarrando el último archivo de compilación nocturno .zip de...

1  Mantener las colinas de los paquetes salientes para arreglar los problemas de retraso del servidor  ( Queuing outgoing packets to fix server lag issues ) 
He estado desarrollando un juego últimamente, mi primer juego multijugador. Estoy usando netty y libgdx . Antes, antes de esta actualización agregué, sol...

2  Simple libgdx pong juego 2.0  ( Simple libgdx pong game 2 0 ) 
He estado trabajando en mi juego de pong y agregué algunas cosas nuevas y agradables. Ahora puede seleccionar la dificultad en la pantalla de inicio y también...

12  Simple libgdx pong juego  ( Simple libgdx pong game ) 
He creado mi primer juego de Java en Libgdx y está funcionando bien, pero estoy 100% seguro de que muchos de mi código pueden ser escritos más cortos que ahor...

13  Generación mundial para el constructor de la ciudad  ( World generation for city builder ) 
Decidí que quería agregar algunas especias a mis mapas de la ciudad con aspecto insípido, así que creé un par de algoritmos para agregar agua al mapa. También...

9  Creando y jugando animaciones para un juego usando libgdx  ( Creating and playing animations for a game using libgdx ) 
Se siente que hay mucho código involucrado para construir manualmente una animación utilizando el marco libgdx. En mi caso específico, estoy creando una serie...

6  Optimización de rendimiento en Box2D con LIBGDX  ( Performance optimization on box2d with libgdx ) 
Hace unos días decidí que quería involucrarme con LibgDX. Hasta ahora, estoy impresionado por lo simple que es conseguir algo en la pantalla para trabajar. ...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos