爐石傳說 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來說,總覺得會出現不兼容的情況。。。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM