about the code

illim edited this page Sep 27, 2013 · 32 revisions

Code and architecture of my program is crap, but in the case you need to understand, here are some explanations:

GameState

The state of the game is serialized in a big object of the class GameState, containing player states, slots, manas, additional effects and datas. A GameState object is immutable. The bot use a tree where nodes store, each one, one instance of this object.
A player is identified by an id, 0 or 1.

GameStateUpdater

The game state is updated by an object of the class GameStateUpdater. The game use one instance of this class, and the bot another one. The difference is that the instance used by the game call a listener who updates the graphics.
The deserialization in the updater is lazy, so when you call updater.players(0), the updater will load only datas from player 0.(I have to check in the background the version of the object every time it's dereferenced)

House and cards

A class is described by an instance of the House class. A house has a houseId(unique) and a house index(4 if special).
Cards are described by Creature or Spell class (CardSpec.scala).
Cards effects are mostly "phase effects" (ex: increase one mana each turn) or "reaction" (ex : deals 5 damage when opponent summon creature). FYI, there's a helper for the simplest effects in GameCardEffect.
Effects are function of type Env => Unit. An Env object has access to the calling Game Updater, and a few aliases. For example "player" refers to the PlayerUpdate object, which is the object used by Game Updater to update the owner state.
Reaction is a class containing specific callbacks. The current slot is injected every time it is used so you can access the selected SlotUpdate object with the "selected" function.
Events managed by the reaction class can be:

  • directly broadcasted from Game Updater to every creature on board
  • broadcasted manually by a House event listener called by the Game updater.

When the house listener is initialized, it's also possible to plug a few callbacks in the updater, via a variable called "update" of class ObservableFunc1.

Example:

Acolyte in the vampire class
spec :
Acolyte 3/21 - when enemy creature receives more than 8 damage, gives owner 1 mana of the same element.

You can check that in updater(SlotUpdate in damageSlot function) that there's no broadcast of the damage to all the creatures on the board, but there's this line added specially for accolyte : slots.player.updater.houseEventListeners.foreach(_.onDamaged(slot.card, amount, this)) This invite you to override in a House event listener specific to vampire the onDamaged function. The code looks like this:

  val Vampire : House = House("Vampires", List(
   ...  
   Creature("Acolyte", Attack(3), 21, "When enemy creature receives more than 8 damage,\ngives owner 1 mana of the same element.", reaction = new AcolyteReaction),  
   ...
   , eventListener = Some(new CustomListener(new VampireEventListener)))

  val acolyte    = Vampire.cards(3)

  class VampireEventListener extends HouseEventListener with AnyDeathEventListener {
    // broadcast any damage
    override def onDamaged(card : Creature, amount : Int, slot : SlotUpdate) {
      if (slot.slots.playerId != player.id){ // if the card is on the opponent side
        player.slots.foreach{ s => // iterate over the owner filleds slots
          val sc = s.get.card // s is a SlotUpdate object, as we use foreach, we are sure it's filled
                              // we can use .get to retrieve safely the SlotState object
          // if it's an acolyte and has reacted
          if (sc == acolyte && sc.reaction.onDamaged(card, amount, slot)){
            // focus on the card
            s.focus(blocking = false)
          }
        }
      }
    }
  }

class AcolyteReaction extends Reaction {
  override def onDamaged(card : Creature, amount : Int, slot : SlotUpdate) = {
    if (amount > 8){
      // the damaged slot is on the enemy side so we need to navigate in the slot object to find the other player object to update our mana.
      slot.slots.player.otherPlayer.houses.incrMana(1, card.houseIndex)
      true
    } else false
  }
}

Example of observing updates:

Stranger X/30 Attack is highest opponent mana...
A variable attack is a function who can depend on state. To reevaluate the attack when state change, I need a callback to set the variable as dirty.

   Creature("Stranger", AttackSources().add(new StrangerAttack), 30, "Attack is highest opponent mana...

class StrangerAttack extends AttackStateFunc {
  def apply(attack : Int, player : PlayerUpdate) : Int = {
    attack + player.otherPlayer.getHouses.maxBy(_.mana).mana
  }
}

class WarpEventListener(inner : HouseEventListener) extends ProxyEventListener(inner) {
    ...

    override def init(p : PlayerUpdate){
      super.init(p)
      p.otherPlayer.houses.update.after{ _ => refreshStranger()  } // When the other player update his houses, the refreshStranger method will be called afterward
    }

    def refreshStranger(){
      if (player.getSlots.values.exists(s => isStranger(s.card))){ // find the stranger card without loading the slots
        player.slots.filleds.withFilter(s => isStranger(s.get.card)).foreach{ s =>
          s.attack.setDirty()
        }
      }
    }
  }

/!\ Be aware that the closure used in "after" function should not use "p" directly, but the function "player" to retrieve the PlayerUpdate, to ensure that the player state is loaded in the updater, at the moment it is called, as the modified player is the opponent. If stranger depended on owner state, you could have use "p" as you would be sure that the player state was loaded.

The bullshit is that setDirty can't trigger an event. (card depending on attack of others can't work)

Clone this wiki locally
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.