OTK就是one turn kill,不过这次我们要谈的OTK是自杀,对就是自己把自己给OTK了。
其实程序没有任何错误,只是恰巧碰上了这么个死循环。
ps:文章最后有代码git地址
发动条件及效果:
奥金尼(奥金尼特效是你的回复生命的牌和技能改为造成等量伤害)
痛苦女王(每当该随从造成伤害,为你的英雄回复等量生命值)
奥金尼在场的时候当痛苦女王发动攻击的时候,进行痛苦女王的特效判定,回复生命1,又触发奥金尼的特效,扣自己1点血,再次触发痛苦女王造成伤害的特效,再次回复生命1,又触发奥金尼特效,扣自己1点血,然后造成死循环,直到自己挂掉为止。
有兴趣的童鞋可以看下面这个视频:http://h.163.com/15/0313/16/AKJOMG4O00314RES.html
当然视频里的猜测是肯定不对的,按照程序员的思路来看应该是进入死循环了。
程序模拟:
我们希望能够模拟一下这个场景,并且尝试去实现一下炉石的战斗逻辑,下面的图就是我们的模拟效果,大致实现了一些卡牌和功能。
基础类设计
首先是baseUnit,这个类实现大部分基础的内容,在这个类基础上衍生了卡片和英雄,然后卡片在衍生出魔法卡片,武器卡片,随从卡片等。
基础类里包含了血量、护甲、圣盾、攻击次数等属性,可以实现一些炉石中的基础攻击,还没有考虑aoe技能的实现和恐狼这样的光环设计。
基本的风怒,actCount可以+1;冲锋,战吼的时候actCount+1,默认的随从上场actCount是0;
不过这些都不是重点,我们的重点是在攻击后的判定如何实现,这里baseUnit里也没有实现随从对拼的逻辑,死亡逻辑,只是搭了个架子。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using HeartStone.Interface; namespace HeartStone.BaseDomain { public class BaseUnit : IUnitAction { public BaseUnit(BaseHero pHero) { OwnerHero = pHero; Name = "未知"; Ac = 0; ActCount = 0; Ad = 0; BaseAd = 0; BaseHP = 0; this.HeroType = HeroType.None; this.Hp = 0; this.Shield = false; } public virtual void Dispose() { Console.WriteLine("{0}死亡", this.Name); return; } public BaseHero OwnerHero { get; set; } /// <summary> /// 职业类型 /// </summary> public HeroType HeroType { get; set; } public string Name { get; set; } private int _Hp; /// <summary> /// 血量 /// </summary> public int Hp { get { return _Hp; } set { //血量最高不得超过基础血量 if (value > BaseHP) _Hp = BaseHP; else _Hp = value; } } /// <summary> /// 基础血量 /// </summary> public int BaseHP { get; set; } private int _Ac { get; set; } /// <summary> /// 护甲 /// </summary> public int Ac { get { return _Ac; } set { //护甲最多不能超过基础血量 if (value > BaseHP) _Ac = BaseHP; else _Ac = value; } } /// <summary> /// 攻击 /// </summary> public int Ad { get; set; } /// <summary> /// 基本攻击 /// </summary> public int BaseAd { get; set; } /// <summary> /// 攻击次数,冲锋上场+1,风怒每回合+1 /// </summary> public int ActCount { get; set; } private bool _Shield = false; /// <summary> /// 圣盾 /// </summary> public bool Shield { get { return _Shield; } set { _Shield = value; } } public virtual void Attack(BaseUnit targeted, BaseUnit from, int ad) { targeted.Defense(targeted, from, ad); } public virtual void Defense(BaseUnit targeted, BaseUnit from, int ad) { if (Shield) Shield = false; else { int acDefend = 0; if (targeted.Ac > 0) { acDefend = targeted.Ac - ad; if (acDefend >= 0) targeted.Ac -= ad; //攻击5,护甲10,10-5大于等于0,表示护甲比攻击高,只要减护甲即可 else { //攻击5,护甲2,2-5=-3,小于0,表示护甲不够,要减 targeted.Ac = 0; targeted.Hp += acDefend; } } else targeted.Hp -= ad; Console.WriteLine("{0}受到{1}的{2}伤害,当前护甲{3},生命{4}", targeted.Name, from.Name, ad, targeted.Ac, targeted.Hp); from.MakeDamage(targeted, from, ad); } } /// <summary> /// 执行治疗 /// </summary> /// <param name="targeted"></param> /// <param name="from"></param> /// <param name="ad"></param> public void Treat(BaseUnit targeted, BaseUnit from, int ad) { var hero = from as BaseHero; bool flag = true; if (hero != null) flag = hero.treatFlag; else flag = from.OwnerHero.treatFlag; if (flag) { targeted.Hp += ad; Console.WriteLine("{0}受到{1}的{2}治疗", targeted.Name, from.Name, ad); } else { if (targeted.Hp >= 0) { //from制造了伤害 Defense(targeted, from, ad); } } } public virtual void MakeDamage(BaseUnit targeted, BaseUnit from, int ad) { return; } } }
奥金尼的实现
奥金尼的效果是你的治疗变成等量的伤害,所以对手的治疗依然是治疗,所以我给英雄增加了一个属性,treatFlag,用于标记这个英雄的治疗状态。
那么卡片自带的治疗效果,譬如大地之环或者老司机呢,所有卡片在上场的时候,也就是new的时候必须要传递一个owner,来表示这张卡是属于谁的,那么就可以很容易的找到这个英雄的治疗状态了。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using HeartStone.BaseDomain; namespace HeartStone.Units.UnitCard { public class OKinny : BaseCard { public OKinny(BaseHero pHero) : base(pHero) { Crystal = 4; BaseCrystal = 4; Ad = 3; BaseAd = 3; BaseHP = 5; Hp = 5; Name = "奥金尼"; //牧师职业卡 HeroType = HeroType.Minister; //奥金尼战场效果 pHero.treatFlag = false; Console.WriteLine("{0}召唤登场",Name); } ~OKinny() { this.OwnerHero.treatFlag = true; } public override void Dispose() { //奥金尼死了要重置 this.OwnerHero.treatFlag = true; base.Dispose(); } } }
痛苦女王的实现
痛苦女王的效果是,造成伤害时回复等量的生命,所以痛苦女王需要重写MakeDamage方法,然后给自己英雄回复等量生命。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using HeartStone.BaseDomain; namespace HeartStone.Units.UnitCard { public class QueenPain : BaseCard { public QueenPain(BaseHero pHero) : base(pHero) { Crystal = 2; BaseCrystal = 2; Ad = 1; BaseAd = 1; BaseHP = 4; Hp = 4; Name = "痛苦女王"; //牧师职业卡 HeroType = HeroType.Minister; Console.WriteLine("{0}召唤登场", Name); } public override void MakeDamage(BaseUnit targeted, BaseUnit from, int ad) { Treat(from.OwnerHero, this, ad); base.MakeDamage(targeted, from, ad); } } }
治疗的实现
治疗的实现时需要判断treatFlag,如果treatFlag为false的话就变成造成等量的伤害,然后再次调用from.MakeDamage方法,这样就可以循环起痛苦女王制造伤害的效果了,当然为了防止真的死循环,这里需要对目标的生命值进行一个判断,如果目标生命值小于0了则停止继续循环。
/// <summary> /// 执行治疗 /// </summary> /// <param name="targeted"></param> /// <param name="from"></param> /// <param name="ad"></param> public void Treat(BaseUnit targeted, BaseUnit from, int ad) { var hero = from as BaseHero; bool flag = true; if (hero != null) flag = hero.treatFlag; else flag = from.OwnerHero.treatFlag; if (flag) { targeted.Hp += ad; Console.WriteLine("{0}受到{1}的{2}治疗", targeted.Name, from.Name, ad); } else { if (targeted.Hp >= 0) { //from制造了伤害 Defense(targeted, from, ad); } } }
英雄和技能的实现
英雄的技能考虑到战士是瞬发的,法师可以选择目标,当然还会有可以只能选自己随从的技能或者只能选英雄的技能,等等。 所以在这里我们加了一个taregtObject,用于标记这个技能可以指向谁。
然后就是实现每个英雄的代码了,我实现了3个英雄,这里展示一下牧师的实现
using System; using System.Collections.Generic; using System.Linq; using System.Text; using HeartStone.BaseDomain; namespace HeartStone.Units.Hero { public class HeroMinister : BaseDomain.BaseHero { /// <summary> /// 构造函数 /// </summary> public HeroMinister() : base() { Name = "牧师"; //初始化自己的技能 HeroSkill = new MinisterSkill(this); } } public class MinisterSkill : BaseHeroSkill { public MinisterSkill(BaseHero pHero) : base(pHero) { TargetObject = TargetedObject.AllUnit; } /// <summary> /// 牧师的技能 /// </summary> public override void Skill(BaseUnit taregted) { Console.WriteLine("{0}使用了技能,目标{1}", thisHero.Name, taregted.Name); this.thisHero.Treat(taregted, thisHero, 2); } } }
模拟效果
然后我们就可以进行一个简单的模拟了,不考虑水晶,直接模拟环境
static void Main(string[] args) { HeroMinister minister = new HeroMinister(); HeroWarrior warrior = new HeroWarrior(); //战士回合,战士使用技能,结束 warrior.HeroSkill.Skill(warrior); Console.WriteLine("================回合结束================"); //牧师回合,牧师上奥金尼,痛苦女王,用治疗伤害战士 OKinny oKinny = new OKinny(minister); QueenPain queenPain = new QueenPain(minister); minister.HeroSkill.Skill(warrior); Console.WriteLine("================回合结束================"); //战士回合,战士使用技能,结束 warrior.HeroSkill.Skill(warrior); Console.WriteLine("================回合结束================"); //牧师回合,奥金尼攻击,痛苦女王攻击,牧师卒 oKinny.Attack(warrior, oKinny, oKinny.Ad); queenPain.Attack(warrior, queenPain, queenPain.Ad); Console.WriteLine("================回合结束================"); Console.ReadKey(); }
写在最后
平时玩的时候觉得炉石挺简单的一个游戏,模拟一下这个场景应该很容易,但真正去实现的时候发现还是有很多地方缺乏考虑,经过一天的修改才最终出来这个模拟的雏形,还有很多对战逻辑没有实现,譬如aoe的法术还没想到什么好的方法来进行实现。
Git:https://git.oschina.net/jangojing/HeartStoneTest.git