public static void CalculateValues(TyState playerState, TyState opponentState, Controller player, Controller opponent, PlayerTask task, Spell spell) { if (_spellDictionary == null) { Init(); } //give reward/punishment if spells cost less/more than usual: float diff = (float)spell.Card.Cost - (float)spell.Cost; playerState.BiasValue += diff * 1.25f; var key = spell.Card.Name; if (_spellDictionary.ContainsKey(key)) { var action = _spellDictionary[key]; action(playerState, opponentState, player, opponent, task, spell); } else if (TyConst.LOG_UNKNOWN_SECRETS) { TyDebug.LogInfo("Unknown spell: " + task.FullPrint()); } }
//Lightning Storm: Deal 2-3 damage to all enemy minions. Overload: (2) private static void LightningStorm(TyState playerState, TyState opponentState, Controller player, Controller opponent, PlayerTask task, Spell spell) { //give punishment when having less than this enemies: const int NUM_ENEMY_TARGETS = 3; playerState.BiasValue += (opponentState.NumMinionsOnBoard - NUM_ENEMY_TARGETS) * 1.25f; }
private static void SpellDamageReward(TyState playerState, TyState opponentState, Controller player, Controller opponent, PlayerTask task, Spell spell, int damage, float reward) { var targetMinion = task.Target as Minion; if (spell.IsAffectedBySpellpower) { damage += player.CurrentSpellPower; } if (targetMinion.HasDivineShield && damage > 1) { //punishment for wasting damage for divine shield playerState.BiasValue -= 5.0f; return; } var targetHealth = targetMinion.Health; int diff = targetHealth - damage; float finalReward = diff * reward; //if the spell kills a minion on point, give additional bonus: if (diff == 0) { finalReward += reward; } playerState.BiasValue += finalReward; }
public float GetStateValue(TyState playerState, TyState enemyState, Controller player, Controller opponent, PlayerTask task) { TyDebug.Assert(IsMyPlayer(player)); TyDebug.Assert(!IsMyPlayer(opponent)); if (EstimateSecretsAndSpells) { TySecretUtil.CalculateValues(playerState, enemyState, player, opponent); TySecretUtil.EstimateValues(enemyState, opponent); var spell = task.TryGetSpell(); if (spell != null && !spell.IsSecret) { TySpellUtil.CalculateValues(playerState, enemyState, player, opponent, task, spell); } } if (HasLost(enemyState)) { return(Single.PositiveInfinity); } else if (HasLost(playerState)) { return(Single.NegativeInfinity); } return(GetStateValueFor(playerState, enemyState) - GetStateValueFor(enemyState, playerState)); }
private static void CorrectHeroAttack(TyState lastPlayerState, TyState lastEnemyState, POGame.POGame lastState, PlayerTask playerTask, ref bool corrected) { var target = playerTask.Target; var attackingHero = lastState.CurrentPlayer.Hero; lastPlayerState.WeaponDurability--; //hero attacks a minion: if (target is Minion) { var targetMinion = target as Minion; MinionTookDamage(targetMinion, lastEnemyState, lastPlayerState, attackingHero.TotalAttackDamage, false, playerTask); //"revenge" damage from the minion to the hero: ComputeDamageToHero(lastPlayerState, attackingHero, targetMinion.AttackDamage); corrected = true; } //hero attacks a hero: else if (target is Hero) { var targetHero = target as Hero; //compute damage to the targetHero: ComputeDamageToHero(lastEnemyState, targetHero, attackingHero.TotalAttackDamage); corrected = true; } }
private static bool CorrectForSummonAndEquip(Card card, TyState ownerState, TyState opponentState) { var text = card.Text; bool success = true; if (text.Contains("Equip")) { int dmg = 0; int durability = 0; if (FindNumberValues(text, ref dmg, ref durability)) { EquipWeapon(ownerState, dmg, durability); success = true; } } if (text.Contains("Summon")) { int dmg = 0; int health = 0; if (FindNumberValues(text, ref dmg, ref health)) { ownerState.MinionValues += TyMinionUtil.ComputeMinionValue(health, dmg, 1); //just assume that the minion has some sort of (unknown) ability: //Testing.TyDebug.LogInfo("Summoned " + dmg + "/" + health); ownerState.MinionValues += 3; success = true; } } return(success); }
//Gain 1 Mana Crystal this turn only. private static void TheCoin(TyState playerState, TyState opponentState, Controller player, Controller opponent, PlayerTask task, Spell spell) { var curMana = player.GetAvailableMana(); var newMana = curMana + 1; bool enablesNewCards = false; for (int i = 0; i < player.HandZone.Count; i++) { IPlayable card = player.HandZone[i]; //if the card can only be played after using the coin, then it is not bad: if (card.Cost > curMana && card.Cost <= newMana) { enablesNewCards = true; break; } } //if the coin does not enable to play new cards, give punishment. if (!enablesNewCards) { playerState.BiasValue -= 100.0f; } }
public static void CalculateValues(TyState playerState, TyState opponentState, Controller player, Controller opponent) { if (_secretDictionary == null) { Init(); } for (int i = 0; i < player.SecretZone.Count; i++) { var secret = player.SecretZone[i]; var key = secret.Card.Name; if (_secretDictionary.ContainsKey(key)) { var action = _secretDictionary[key]; action(playerState, opponentState, player, opponent, secret); } else { if (TyConst.LOG_UNKNOWN_SECRETS) { TyDebug.LogWarning("Unknown secret: " + secret.Card.FullPrint()); } playerState.BiasValue += secret.Card.Cost * SECRET_VALUE_FACTOR; } } }
/// <summary> Gives points for having cards in the hand. </summary> private float GetHandValues(TyState state) { int firstThree = Math.Min(state.NumHandCards, 3); int remaining = Math.Abs(state.NumHandCards - firstThree); //3 times the points for the first three cards, 2 for all remaining cards: return(3 * firstThree + 2 * remaining); }
//After your opponent plays a minion, add two copies of it to_your hand. private static void FrozenClone(TyState playerState, TyState opponentState, Controller player, Controller opponent, Spell secret) { var mana = opponent.GetAvailableMana(); var minion = TyMinionUtil.EstimatedValueFromMana(mana); //dont multiply by 2, because player still has to play the minions: playerState.BiasValue += minion * 1.75f + TyStateUtility.LateReward(mana, 4, 4.0f); }
private static void ComputeDamageToHero(TyState targetHeroState, Hero targetHero, int damageToHero) { int armor = targetHero.Armor; int dmgAfterArmor = Math.Max(0, damageToHero - armor); targetHeroState.HeroArmor = Math.Max(0, armor - damageToHero); targetHeroState.HeroHealth = Math.Max(0, targetHero.Health - dmgAfterArmor); }
//After your opponent plays a minion, summon a copy of it. private static void MirrorEntity(TyState playerState, TyState opponentState, Controller player, Controller opponent, Spell secret) { var mana = opponent.GetAvailableMana(); var minion = TyMinionUtil.EstimatedValueFromMana(mana); playerState.BiasValue += TyStateUtility.LateReward(mana, 4, 5.0f); playerState.MinionValues += minion; }
private bool HasLost(TyState player) { if (player.HeroHealth <= 0) { return(true); } return(false); }
private static void CorrectHeroPower(TyState lastPlayerState, TyState lastEnemyState, POGame.POGame lastState, PlayerTask task, ref bool corrected) { //Dagger mastery from rogue: equip 1/2 daggers if (lastState.CurrentPlayer.HeroClass == CardClass.ROGUE) { EquipWeapon(lastPlayerState, 1, 2); corrected = true; } }
//When an enemy casts a spell on a minion, summon a 1/3 as the new target. private static void Spellbender(TyState playerState, TyState opponentState, Controller player, Controller opponent, Spell secret) { var myMana = player.GetAvailableMana(); var possibleAverageMinion = TyMinionUtil.EstimatedValueFromMana(myMana); var myAverageMinion = playerState.GetAverageMinionValue(); //dont play if my minions are weaker than a "good" minion at that point in game, also punish when played early: playerState.BiasValue += (myAverageMinion - possibleAverageMinion) + TyStateUtility.LateReward(myMana, 4, 2.0f); }
//Maelstrom Portal: Deal 1 damage to all enemy minions. Summon a random 1-Cost minion. private static void MaelstromPortal(TyState playerState, TyState opponentState, Controller player, Controller opponent, PlayerTask task, Spell spell) { const int NUM_TARGET_MINIONS = 3; //negative if below NUM_TARGET_MINIONS, neutral at NUM_TARGET_MINIONS, then positive: int diff = opponentState.NumMinionsOnBoard - NUM_TARGET_MINIONS; playerState.BiasValue += diff * 1.25f; }
/// <summary> Gives points for clearing the minion zone of the given opponent. </summary> private float GetEmptyFieldValue(TyState state) { //its better to clear the board in later stages of the game (more enemies might appear each round): if (state.NumMinionsOnBoard == 0) { return(2.0f + Math.Min((float)state.TurnNumber, 10.0f)); } return(0.0f); }
private float GetStateValueFor(TyState player, TyState enemy) { float emptyFieldValue = Weights.GetWeight(TyStateWeights.WeightType.EmptyField) * GetEmptyFieldValue(enemy); float healthValue = Weights.GetWeight(TyStateWeights.WeightType.HealthFactor) * GetHeroHealthArmorValue(player); float deckValue = Weights.GetWeight(TyStateWeights.WeightType.DeckFactor) * GetDeckValue(player); float handValue = Weights.GetWeight(TyStateWeights.WeightType.HandFactor) * GetHandValues(player); float minionValue = Weights.GetWeight(TyStateWeights.WeightType.MinionFactor) * GetMinionValues(player); float biasValues = Weights.GetWeight(TyStateWeights.WeightType.BiasFactor) * GetBiasValue(player); return(emptyFieldValue + deckValue + healthValue + handValue + minionValue + biasValues); }
//Lightning Bolt: Deal 3 damage. Overload: (1) private static void LightningBolt(TyState playerState, TyState opponentState, Controller player, Controller opponent, PlayerTask task, Spell spell) { if (task.HasTarget) { //reward if the spell does NOT overkill an enemy: if (task.Target is Minion) { SpellDamageReward(playerState, opponentState, player, opponent, task, spell, 3, 1.25f); } } }
//After your opponent plays a minion, deal $6 damage to it and any excess to their hero private static void ExplosiveRunes(TyState playerState, TyState opponentState, Controller player, Controller opponent, Spell secret) { //doesnt matter if played early or late (early: deals damage to hero, later will most likely kill a minion) //multiply with a factor because either it kills a minion (higher value than just the damage dealt) //or/and it deals damage to the hero (also worth more than just reducing the hp) const float FACTOR = 2.0f; const int BASE_DAMAGE = 6; opponentState.BiasValue -= ((BASE_DAMAGE + player.CurrentSpellPower) * FACTOR); }
//When a minion attacks your hero, destroy it. private static void Vaporize(TyState playerState, TyState opponentState, Controller player, Controller opponent, Spell secret) { var opponentMana = opponent.GetAvailableMana(); //punish playing early: playerState.BiasValue += TyStateUtility.LateReward(opponentMana, 5, 5.0f); //estimate destroying an enemy minion: float avgMinionValue = TyMinionUtil.EstimatedValueFromMana(opponentMana); opponentState.MinionValues -= avgMinionValue; }
public static TyState FromSimulatedGame(POGame.POGame newState, Controller me, PlayerTask task) { TyState s = new TyState { HeroHealth = me.Hero.Health, HeroArmor = me.Hero.Armor, TurnNumber = newState.Turn, NumDeckCards = me.DeckZone.Count, NumHandCards = me.HandZone.Count, NumMinionsOnBoard = me.BoardZone.Count, Fatigue = me.Hero.Fatigue, MinionValues = TyMinionUtil.ComputeMinionValues(me) }; if (me.Hero.Weapon != null) { s.WeaponDurability = me.Hero.Weapon.Durability; s.WeaponDamage = me.Hero.Weapon.AttackDamage; } //this case is met, if the player uses a card that temporarily boosts attack: if (me.Hero.TotalAttackDamage > s.WeaponDamage) { s.WeaponDamage = me.Hero.TotalAttackDamage; //assume that the player can at least attack once: if (s.WeaponDurability == 0) { s.WeaponDurability = 1; } } //aka, can't attack: if (me.Hero.IsFrozen) { s.WeaponDamage = 0; } var minion = task.TryGetMinion(); if (minion != null) { //give reward/punishment of minions cost less/more than usual: float diff = (float)minion.Card.Cost - (float)minion.Cost; s.BiasValue += diff * 1.5f; } return(s); }
//After your opponent plays a minion, transform it into a 1/1 Sheep. private static void PotionOfPolymorph(TyState playerState, TyState opponentState, Controller player, Controller opponent, Spell secret) { int opponentMana = opponent.GetAvailableMana(); //punish playing early: playerState.BiasValue += TyStateUtility.LateReward(opponentMana, 5, 5.0f); //value is the difference between an average minion and the sheep: float sheepValue = TyMinionUtil.ComputeMinionValue(1, 1, 1); float averageMinionValue = TyMinionUtil.EstimatedValueFromMana(opponentMana); float polymorphedValue = (sheepValue - averageMinionValue); opponentState.MinionValues += polymorphedValue; }
private static void RemoveMinion(Minion minion, TyState ownerState, TyState opponentState, PlayerTask task) { //remove the minion value from the overall minion values and remove it from the board ownerState.MinionValues -= TyMinionUtil.ComputeMinionValue(minion); ownerState.NumMinionsOnBoard--; if (minion.HasDeathrattle) { if (!CorrectForSummonAndEquip(minion.Card, ownerState, opponentState) && TyConst.LOG_UNKNOWN_CORRECTIONS) { TyDebug.LogError("Unknown deathrattle from " + minion.Card.FullPrint()); TyDebug.LogWarning("After task " + task.FullPrint()); } } }
/// <summary> Estimates how good the given child state is. </summary> public static float GetStateValue(POGame.POGame parent, POGame.POGame child, PlayerTask task, TyStateAnalyzer analyzer) { float valueFactor = 1.0f; TyState myState = null; TyState enemyState = null; Controller player = null; Controller opponent = null; //it's a buggy state, mostly related to equipping/using weapons on heroes etc. //in this case use the old state and estimate the new state manually: if (child == null) { player = parent.CurrentPlayer; opponent = parent.CurrentOpponent; myState = TyState.FromSimulatedGame(parent, player, task); enemyState = TyState.FromSimulatedGame(parent, opponent, null); //if the correction failes, assume the task is x% better/worse: if (!TyState.CorrectBuggySimulation(myState, enemyState, parent, task)) { valueFactor = 1.25f; } } else { player = child.CurrentPlayer; opponent = child.CurrentOpponent; //happens sometimes even with/without TURN_END, idk if (!analyzer.IsMyPlayer(player)) { player = child.CurrentOpponent; opponent = child.CurrentPlayer; } myState = TyState.FromSimulatedGame(child, player, task); enemyState = TyState.FromSimulatedGame(child, opponent, null); } TyDebug.Assert(analyzer.IsMyPlayer(player)); TyDebug.Assert(!analyzer.IsMyPlayer(opponent)); return(analyzer.GetStateValue(myState, enemyState, player, opponent, task) * valueFactor); }
//When your hero takes fatal damage, prevent it and become Immune this turn. private static void IceBlock(TyState playerState, TyState opponentState, Controller player, Controller opponent, Spell secret) { //give punishment when at full hp, give reward if hp lessens const int MAX_HEALTH = 30; const int MIN_HEALTH = 1; float healthPercent = 1.0f - TyUtility.InverseLerp(playerState.HeroHealth, MIN_HEALTH, MAX_HEALTH); //punishment when at full hp: const float MIN_VALUE = -30.0f; //reward when at 1 hp: const float MAX_VALUE = 45.0f; float value = TyUtility.Lerp(MIN_VALUE, MAX_VALUE, healthPercent); playerState.BiasValue += value; }
private static void EquipWeapon(TyState lastPlayerState, int attackDamage, int durability) { int newSum = attackDamage + durability; int oldSum = lastPlayerState.WeaponDamage + lastPlayerState.WeaponDurability; //equipping a worse weapon: if (newSum > 0 && newSum <= oldSum) { //assign negative values to indicate for a bad move (kinda hacky to do it that way): lastPlayerState.WeaponDamage = -Math.Abs(lastPlayerState.WeaponDamage); lastPlayerState.WeaponDurability = -Math.Abs(lastPlayerState.WeaponDurability); } //equip either 0/0 (destroy) or a better weapon: else { lastPlayerState.WeaponDamage = attackDamage; lastPlayerState.WeaponDurability = durability; } }
public static bool CorrectBuggySimulation(TyState lastPlayerState, TyState lastEnemyState, POGame.POGame lastState, PlayerTask task) { var taskType = task.PlayerTaskType; //Testing.TyDebug.LogError(task.FullPrint()); bool corrected = false; if (taskType == PlayerTaskType.END_TURN) { CorrectTurnEnd(lastPlayerState, lastEnemyState, lastState, task, ref corrected); } else if (taskType == PlayerTaskType.HERO_ATTACK) { CorrectHeroAttack(lastPlayerState, lastEnemyState, lastState, task, ref corrected); } else if (taskType == PlayerTaskType.PLAY_CARD) { CorrectPlayCard(lastPlayerState, lastEnemyState, lastState, task, ref corrected); } else if (taskType == PlayerTaskType.MINION_ATTACK) { CorrectMinionAttack(lastPlayerState, lastEnemyState, lastState, task, ref corrected); } else if (taskType == PlayerTaskType.HERO_POWER) { CorrectHeroPower(lastPlayerState, lastEnemyState, lastState, task, ref corrected); } if (TyConst.LOG_UNKNOWN_CORRECTIONS && !corrected) { TyDebug.LogError("Unknown buggy PlayerTask: " + task.FullPrint()); } return(corrected); }
private static void CorrectMinionAttack(TyState lastPlayerState, TyState lastEnemyState, POGame.POGame lastState, PlayerTask task, ref bool corrected) { if (task.HasSource) { if (task.Source is Minion) { var minionSource = task.Source as Minion; if (task.HasTarget) { if (task.Target is Minion) { var targetMinion = (task.Target as Minion); MinionTookDamage(targetMinion, lastEnemyState, lastPlayerState, minionSource.AttackDamage, minionSource.Poisonous, task); MinionTookDamage(minionSource, lastPlayerState, lastEnemyState, targetMinion.AttackDamage, targetMinion.Poisonous, task); corrected = true; } } } } }
private static void MinionTookDamage(Minion targetMinion, TyState ownerState, TyState opponentState, int totalAttackDamage, bool poison, PlayerTask task) { //didn't take damage: if (targetMinion.HasDivineShield) { ownerState.MinionValues -= TyMinionUtil.DIVINE_SHIELD_VALUE; } else { int damage = totalAttackDamage; if (poison || damage >= targetMinion.Health) { RemoveMinion(targetMinion, ownerState, opponentState, task); } else { ownerState.MinionValues -= damage; } } }