炉石传说 C# 设计文档(序)

经过3个月的开发,有很多感触。

以前一直以为技术是开发成败的第一因素,现在发现,等到你代码写的时间够长,经验够丰富,什么功能都能随手完成,对于业务的分析能力变成了第一位。

炉石山寨版的BS版本用到的HTML5的SVG,我看了一个下午的教程,借鉴以前GUI+和HTML的经验,很快就能写点东西出来了。

WebSocket,Github上找了一个开源的C#项目,通讯这块也是几个小时就搞定了。Javascript不是很熟悉,当时闭包这样的一些概念也算听说过,Js也是无障碍就写成了。

整个项目的技术壁垒其实不是很高,难的是对于炉石的业务的理解。

设计一个项目,就是将项目分割成若干个子系统,然后用适当的设计模式,让代码出现在它该出现的地方。

一个项目的代码量,肯定是不断增加,然后通过重构减少,然后又加入新的子系统,导致代码量的增加,再通过重构减少,这样的螺旋形的折腾和反复(迭代)使得代码越来越完善。

同时,随着对于业务的理解,很有可能会对原来的设计产生颠覆性的修改。炉石的开发中,我3次做了颠覆性的设计的修改。

血的教训告诉我们,开发之前,一定要做好业务的研究。

如果你觉得你的代码这样写也不好,那样写也不对,不用纠结了,好好睡一觉,然后重新整理一下业务,修改一下你的业务模型,然后所有问题就迎刃而解了。

 

中国的外包公司,包括一些有名的大公司,都不喜欢写文档,或者文档落后与代码。

代码可以上线,可以取得业绩,文档似乎完全只是为了应付CMM的规定,所以,别说考虑文档的美观了,就是一个和代码同步的文档,对于大多数公司来说,也很奢侈。

很多人有一个观点,好的代码是不需要注释和文档的,代码就是最好的注释和文档。

其实,我觉得,文档不是伪代码,应该是对于业务的一种解释,以及编码的一个依据。

我觉得需要这些枚举,是通过怎样的调查分析得到的结论。

我觉得业务流程是怎样的,我通过分析业务,画出流程图得到的一个结果。

很多人说,炉石的C#代码通过反编译可以看得到,为什么要重复去山寨呢?

这段时间工作很空闲,写代码写得旧了,所以想通过山寨炉石来提高自己的分析和设计能力。

炉石其实你真正的考虑怎么设计的好,怎么使得你的设计可以同时满足BS,CS,网络版,单机版,也是非常不容易的。然后炉石的业务,如果很多地方不考虑扩展性,也不是很复杂,一个月足够完成所有的编码工作了。

但是,如果要考虑到扩展性,考虑到重复使用,考虑到IOC这样的东西,则需要好好考虑的。

例如,施放法术,

我写到最后就是一个施放法术的接口(抽象),然后各种法术的具体实现,

施法流程是调用了施放法术的接口,通过法术数据去调用具体的实现。

法术接口:

(博客园的插入代码推荐有了,旁边那个插入代码没有存在的意义了,个人觉得)

using Engine.Action;
using Engine.Card;
using Engine.Client;
using System;
using System.Collections.Generic;

namespace Engine.Effect
{
    public interface IAtomicEffect
    {
        /// <summary>
        /// 对方复原操作
        /// </summary>
        /// <param name="game"></param>
        /// <param name="actField"></param>
        void ReRunEffect(ActionStatus game, String[] actField);
        /// <summary>
        /// 对英雄动作
        /// </summary>
        /// <param name="game"></param>
        /// <param name="PlayInfo"></param>
        /// <returns></returns>
        String DealHero(ActionStatus game, PublicInfo PlayInfo);
        /// <summary>
        /// 对随从动作
        /// </summary>
        /// <param name="game"></param>
        /// <param name="Minion"></param>
        /// <returns></returns>
        String DealMinion(ActionStatus game, MinionCard Minion);
        /// <summary>
        /// 获得效果信息
        /// </summary>
        /// <param name="InfoArray"></param>
        void GetField(List<String> InfoArray);
    }
}

一个伤害效果

using Engine.Action;
using Engine.Client;
using Engine.Control;
using Engine.Utility;
using System;
using System.Collections.Generic;

namespace Engine.Effect
{
    /// <summary>
    /// 攻击效果
    /// </summary>
    public class AttackEffect : IAtomicEffect
    {
        /// <summary>
        /// 效果表达式
        /// </summary>
        public String 伤害效果表达式 = String.Empty;
        /// <summary>
        /// 伤害加成
        /// </summary>
        public Boolean 伤害加成 = false;
        /// <summary>
        /// 获得效果信息
        /// </summary>
        /// <param name="InfoArray"></param>
        void IAtomicEffect.GetField(List<string> InfoArray)
        {
            伤害效果表达式 = InfoArray[0];
            伤害加成 = ExpressHandler.GetBooleanExpress(InfoArray[1]);
        }
        /// <summary>
        /// 对英雄动作
        /// </summary>
        /// <param name="game"></param>
        /// <param name="PlayInfo"></param>
        /// <returns></returns>
        String IAtomicEffect.DealHero(ActionStatus game, Client.PublicInfo PlayInfo)
        {
            int AttackPoint = ExpressHandler.GetEffectPoint(game, 伤害效果表达式);
            //调整伤害值
            if (伤害加成) AttackPoint += game.AllRole.MyPublicInfo.BattleField.AbilityDamagePlus;
            if (PlayInfo.Hero.AfterBeAttack(AttackPoint))
            {
                game.battleEvenetHandler.事件池.Add(new Engine.Utility.CardUtility.全局事件()
                {
                    触发事件类型 = CardUtility.事件类型枚举.受伤,
                    触发位置 = PlayInfo.Hero.战场位置
                });
            }
            return Server.ActionCode.strAttack + CardUtility.strSplitMark + PlayInfo.Hero.战场位置.ToString() + CardUtility.strSplitMark + AttackPoint.ToString();
        }
        /// <summary>
        /// 对随从动作
        /// </summary>
        /// <param name="game"></param>
        /// <param name="Minion"></param>
        /// <returns></returns>
        String IAtomicEffect.DealMinion(ActionStatus game, Card.MinionCard Minion)
        {
            int AttackPoint = ExpressHandler.GetEffectPoint(game, 伤害效果表达式);
            //调整伤害值
            if (伤害加成) AttackPoint += game.AllRole.MyPublicInfo.BattleField.AbilityDamagePlus;
            if (Minion.设置被攻击后状态(AttackPoint))
            {
                game.battleEvenetHandler.事件池.Add(new Engine.Utility.CardUtility.全局事件()
                {
                    触发事件类型 = CardUtility.事件类型枚举.受伤,
                    触发位置 = Minion.战场位置
                });
            }
            return Server.ActionCode.strAttack + CardUtility.strSplitMark + Minion.战场位置.ToString() + CardUtility.strSplitMark + AttackPoint.ToString();
        }
        /// <summary>
        /// 对方复原操作
        /// </summary>
        /// <param name="game"></param>
        /// <param name="actField"></param>
        void IAtomicEffect.ReRunEffect(ActionStatus game, string[] actField)
        {
            int AttackPoint = int.Parse(actField[3]);
            if (actField[1] == CardUtility.strYou)
            { 
                //MyInfo
                if (actField[2] == Client.BattleFieldInfo.HeroPos.ToString("D1"))
                {
                    game.AllRole.MyPublicInfo.Hero.AfterBeAttack(AttackPoint);
                }
                else
                {
                    game.AllRole.MyPublicInfo.BattleField.BattleMinions[int.Parse(actField[2]) - 1].设置被攻击后状态(AttackPoint);
                }
            }
            else
            {
                //YourInfo
                if (actField[2] == Client.BattleFieldInfo.HeroPos.ToString("D1"))
                {
                    game.AllRole.MyPublicInfo.Hero.AfterBeAttack(AttackPoint);
                }
                else
                {
                    game.AllRole.MyPublicInfo.BattleField.BattleMinions[int.Parse(actField[2]) - 1].设置被攻击后状态(AttackPoint);
                }
            }
        }
    }
}
        /// <summary>
        /// 实施效果
        /// </summary>
        /// <param name="singleEffect"></param>
        /// <param name="game"></param>
        /// <param name="RandomSeed"></param>
        /// <returns></returns>
        public static List<string> RunSingleEffect(EffectDefine singleEffect, ActionStatus game, int RandomSeed)
        {
            List<string> Result = new List<string>();
            List<string> PosList = SelectUtility.GetTargetList(singleEffect.AbliltyPosPicker, game, RandomSeed);
            foreach (string PosInfo in PosList)
            {
                var PosField = PosInfo.Split(CardUtility.strSplitMark.ToCharArray());
                var strResult = string.Empty;
                if (PosField[0] == CardUtility.strMe)
                {
                    switch (int.Parse(PosField[1]))
                    {
                        case BattleFieldInfo.HeroPos:
                            Result.Add(GetEffectHandler(singleEffect, game, CardUtility.strMe + CardUtility.strSplitMark + BattleFieldInfo.HeroPos.ToString("D1")).DealHero(game, game.AllRole.MyPublicInfo));
                            break;
                        case BattleFieldInfo.AllMinionPos:
                            for (int i = 0; i < game.AllRole.MyPublicInfo.BattleField.MinionCount; i++)
                            {
                                Result.Add(GetEffectHandler(singleEffect, game, CardUtility.strMe + CardUtility.strSplitMark + (i + 1).ToString("D1")).DealMinion(game, game.AllRole.MyPublicInfo.BattleField.BattleMinions[i]));
                            }
                            break;
                        case BattleFieldInfo.AllRolePos:
                            Result.Add(GetEffectHandler(singleEffect, game, CardUtility.strMe + CardUtility.strSplitMark + BattleFieldInfo.HeroPos.ToString("D1")).DealHero(game, game.AllRole.MyPublicInfo));
                            for (int i = 0; i < game.AllRole.MyPublicInfo.BattleField.MinionCount; i++)
                            {
                                Result.Add(GetEffectHandler(singleEffect, game, CardUtility.strMe + CardUtility.strSplitMark + (i + 1).ToString("D1")).DealMinion(game, game.AllRole.MyPublicInfo.BattleField.BattleMinions[i]));
                            }
                            break;
                        default:
                            Result.Add(GetEffectHandler(singleEffect, game, PosInfo).DealMinion(game, game.AllRole.MyPublicInfo.BattleField.BattleMinions[int.Parse(PosField[1]) - 1]));
                            break;
                    }
                }
                else
                {
                    switch (int.Parse(PosField[1]))
                    {
                        case BattleFieldInfo.HeroPos:
                            Result.Add(GetEffectHandler(singleEffect, game, CardUtility.strYou + CardUtility.strSplitMark + BattleFieldInfo.HeroPos.ToString("D1")).DealHero(game, game.AllRole.YourPublicInfo));
                            break;
                        case BattleFieldInfo.AllMinionPos:
                            for (int i = 0; i < game.AllRole.YourPublicInfo.BattleField.MinionCount; i++)
                            {
                                Result.Add(GetEffectHandler(singleEffect, game, CardUtility.strYou + CardUtility.strSplitMark + (i + 1).ToString("D1")).DealMinion(game, game.AllRole.YourPublicInfo.BattleField.BattleMinions[i]));
                            }
                            break;
                        case BattleFieldInfo.AllRolePos:
                            Result.Add(GetEffectHandler(singleEffect, game, CardUtility.strYou + CardUtility.strSplitMark + BattleFieldInfo.HeroPos.ToString("D1")).DealHero(game, game.AllRole.YourPublicInfo));
                            for (int i = 0; i < game.AllRole.YourPublicInfo.BattleField.MinionCount; i++)
                            {
                                Result.Add(GetEffectHandler(singleEffect, game, CardUtility.strYou + CardUtility.strSplitMark + (i + 1).ToString("D1")).DealMinion(game, game.AllRole.YourPublicInfo.BattleField.BattleMinions[i]));
                            }
                            break;
                        default:
                            Result.Add(GetEffectHandler(singleEffect, game, PosInfo).DealMinion(game, game.AllRole.YourPublicInfo.BattleField.BattleMinions[int.Parse(PosField[1]) - 1]));
                            break;
                    }
                }
            }
            return Result;
        }

最后,通过对于业务的不断理解,有一些看上去不一样的东西变得一样了。

法术,光环,战吼,亡语,其实都是一样的

一些看上去一样的东西,变得不一样了

奥秘有的是修改触发行为,有的是追加效果;光环有的是影响战场的其他随从,有的是被战场影响

等到你将业务真正分析清楚了,写代码就是一个体力活了。

而且,作为程序员,修改自己的代码,大家都不情愿,辛辛苦苦写的代码不愿意修改。

如果文档先行,修改一下文档,大家还是很乐意的,不用测试,不用返工。而且修改文档是时间成本最小的。如果上线后再修改BUG,那个时间成本。。。。。

题外话:

VS14CTP已经开始使用了,ASPNET的vNext版本也开始尝试了,然后KRE的Self-Host功能也做个试验了,部署在远程服务器上,本地完全可以访问。

但是,性能不能和原生的IIS相比,园子里面有一篇文章介绍过的,地址忘记了,大概相差两三倍吧。

 

第一次玩MVC(技术面试的时候,这个好像一定要问的,没有玩过MVC好像就不懂ASP一样,一直纯手工写WebForm的人,情何以堪,IOC好像也是必问的,现在的技术面试太看重理论了)

,蛮有趣的,但是我有个疑问,MVC加上EF对于关系型数据库支持的很少,如何让MVC和NOSQL一起工作呢?

NOSQL的元数据对于MVC来说,总觉得会出现不兼容的情况。。。。

posted @ 2014-08-05 17:15  灰毛毛  阅读(2492)  评论(4编辑  收藏  举报