最近在大連的同事強力推薦我玩 爐石傳說,一個卡牌游戲。加上五一放一個很長很長的假期,為了磨練自己,決定嘗試開發一個C#的爐石傳說。
這件事情有人已經干過了,開發了一個網頁版的爐石,但是貌似不能玩。。。。
http://cnodejs.org/topic/529c1366a6957a0809485f3d
如果這位同志看到這篇文章,請一定和我聯系?。?/P>
rudermail@QQ.com或Q我377372779
第一天
開始學習爐石傳說的玩法,最好的方法是不停的玩游戲。
一個應用是否邏輯清晰,取決于你對于業務的了解程度,一般到開發后期發現有些邏輯內聚和耦合發生問題,往往都是前期對于業務的理解不夠透徹。
很多開發都往往是隨著業務邏輯的了解,進行不停的重構,當然,這個也是一個必然的過程,但是如果能夠在前期就了解業務的話,則可以節約后期大量的時間。
由于長期做對日軟件的緣故,式樣書,設計書先行是根深蒂固的思想。所有設想都先以文字或者偽代碼的形式寫下來,進行一些假想的驗證。
整個項目的平衡感,脈絡,各個模塊,層次結構都在這個時候定下來。這個時候是修改成本最低的階段,等到后期這些模塊再重新劃分,風險就高了。
通過第一天的學(you)習(xi),大概整理除了一個脈絡:
(文字版本的不是很好看,下面的Excel版本,瀏覽器也看不到。。)
卡牌基礎
法術卡牌
熟讀各種法術牌,講法術牌分類
隨從卡牌
各種特性的整理,可以參考各種網絡上的資料
武器卡牌
比較簡單的類型
游戲環境
英雄
生命值
基本技能
武器
牌堆
套牌
手牌
手里的牌
戰場
7個位置的隨從
法力水晶
由于某些卡牌會改變水晶,也為了細化系統,法力水晶升級為一個獨立的類
第二天
開始進行Coding。由于英語不是很好,有一些單詞不知道,然后開始中英文夾雜編碼。
很久前,也討論過中文編程的問題,其實很多變量,用中文還是英語完全沒有限制。
寫代碼只要能讓自己和維護的人讀得懂就可以了。畢竟即使你用英語變量,你的注釋還是中文的。。。
當然,如果你想讓代碼能夠國際化,特別是開源項目,能用標準的英語來寫代碼是極好的。
NUnit用的不是很好,所以,自己寫了一些GUI的界面來做一些簡單的UT測試。
第三天
代碼的重構,設計書和代碼的同步。
很多項目,在一開始的時候還有設計書,然后在開發的時候,往往重構好代碼后,設計書還是重構之前的樣子。
IDE可以自動重構代碼,但是不能自動重構設計書。。。。
國內項目不注重文檔,所以這種情況很常見。日系的開發,設計書則相當重要,一個是為了日后維護能有個依據,二是為了能夠明確責任。
這個地方為什么要修改,對于整體項目有什么影響,都能從設計書的修改履歷中看出端倪。
代碼和設計書同步的時候,也是一個反思的機會,看看現階段寫的代碼,是不是很干凈優雅,
往往將代碼轉換為設計書的時候,可以看到代碼的問題。特別是代碼的一致性上,散落在不同地方的代碼,經過整理,用#region歸納后,可以看到很多問題。
第四天
爐石C#版本不是短時間內可以完成的,在完成整個爐石之前,可以考慮用當前的代碼,先制作一些小的工具。
一來可以拉攏人氣,隔一段時間有個小的可以檢證的成果物,不至于半途而廢;
二來,小工具的制作也是為了爐石服務的,有些小工具的代碼也可以反饋到爐石主體代碼。
我向來反對一開始就要做個了不起的東西,或者只開發了不起的東西,忘記了留下二次開發的接口或者周邊產品的接口。
魔法的定義
魔法類型
攻擊
回復
召喚
卡牌 奧術智慧
變形 變羊術
水晶 幸運幣
奧秘
魔法關系
或者 抉擇系:例如:抉擇: 對一個隨從造成3點傷害;或者造成1點傷害并抽一張牌。
并且 有副作用的魔法,例如:造成4點傷害,隨機棄一張牌。
目標選擇模式
隨機
全體
指定
目標選擇方向
本方
對方
無限制
目標選擇角色
隨從
英雄
全體
標準效果點數
傷害效果點數、治療效果點數、抽牌數
實際效果點數
由于某些卡牌效果會影響效果點數
效果回數
例如:奧術飛彈是3次1點傷害
附加信息
難以用上面的規則的卡牌,特殊的附加信息
奧術智慧的定義:
1.有一個效果:抽兩張牌
2.成本是1點
3.對象時本方
/// <summary> /// 初始化奧術智慧 /// </summary> /// <returns></returns> public static Card.MagicCard Get奧術智慧() { Card.MagicCard 奧術智慧 = new Card.MagicCard(); 奧術智慧.SN = "M000002"; 奧術智慧.Name = "奧術智慧"; 奧術智慧.Description = "隨機抽兩張牌。"; 奧術智慧.Rare = Card.CardBasicInfo.稀有程度.綠色; //使用成本 奧術智慧.ActualCostPoint = 1; 奧術智慧.StandardCostPoint = 1; 奧術智慧.JoinType = Card.MagicCard.EffectJoinType.None; //隨機抽兩張牌 Card.MagicCardStockEffect cardStockEffect = new Card.MagicCardStockEffect(); cardStockEffect.StandardEffectPoint = 2; cardStockEffect.EffectCount = 1; cardStockEffect.EffectTargetSelectDirect = Card.CardUtility.TargetSelectDirectEnum.本方; 奧術智慧.FirstMagicDefine = cardStockEffect; return 奧術智慧; }
第五天
我一直在考慮,AI是不是能代替人。
爐石這樣的游戲,有許多常用的套路,只要組好了套牌,然后能夠將很多常用的卡牌組合,優先策略教授給AI,應該可以做到和人對戰。
和國際象棋,圍棋比起來,爐石這樣的游戲,勝利無非是:運氣好,套牌組的合理,正確衡量場面上各種對方卡牌的威脅程度,熟練使用各種套路,有耐心,不犯低級錯誤。
運氣好,套牌組的合理,這個事情,前者人和AI都一樣,套牌可以人組好后直接給AI使用。
正確衡量場面上各種對方卡牌的威脅程度:這個也不難,其實卡牌的使用成本已經是一個可以量化的威脅度指標了。
熟練使用各種套路:對方出了一個 10/10 (合理的閥值)的家伙,如果有變羊術,就變掉;對手一大堆血量3,4的隨從,就用清場的牌,這些套路也很直觀
有耐心,不犯低級錯誤:這個是AI的長處,AI絕對不會忘記還有魔法可以直接 打臉,還有可以使用的隨從去 打臉
當然,對于頂級高手AI還不是可以簡單的取勝,審時度勢,及時調整戰略的能力,人還是有著無可比擬的優勢。
第六天
看看客戶端和服務器端分工如何:
順便提一句,日常文檔的編寫,wps不比Office差,支持國貨
核心庫 Card.DLL 還有客戶端,服務器,之間要連接起來
客戶端-服務器-核心庫 核心庫:委托形式 /// <summary> /// 抽牌委托 /// </summary> /// <param name="IsFirst">先后手區分</param> /// <param name="magic">法術定義</param> public delegate List<CardBasicInfo> delegateDrawCard(Boolean IsFirst, int DrawCount); /// <summary> /// 抽牌魔法(服務器方法) /// </summary> public static delegateDrawCard DrawCard; 客戶端:實現委托 /// <summary> /// 初始化 /// </summary> public static void Init() { //抽牌的具體方法 CardUtility.DrawCard += DrawCardAtServer; } /// <summary> /// 抽牌(服務器方法) /// </summary> /// <returns></returns> public static List<String> DrawCardAtServer(Boolean IsFirst, int Count) { //向服務器提出請求,獲得牌 return GameStatus.DrawCard(IsFirst,Count); } 服務器端:實際操作牌堆 /// <summary> /// 抽牌 /// </summary> /// <param name="IsFirst"></param> /// <param name="Count"></param> /// <returns></returns> public static List<String> DrawCard(Boolean IsFirst, int Count) { var targetStock = IsFirst ? FirstCardStock : SecondCardStock; return targetStock.DrawCard(Count); } (調用Card核心庫方法) /// <summary> /// 抽卡 /// </summary> /// <param name="CardCount"></param> /// <returns></returns> public List<String> DrawCard(int CardCount) { List<String> newList = new List<String>(); for (int i = 0; i < CardCount; i++) { if (CardList.Count == 0) break; newList.Add(CardList.Pop()); } return newList; }
第七天
考慮服務器和客戶端的開發。
客戶端-服務器通信
TCP協議,類似于網站那樣的短連接。
玩家A 服務器消息區 玩家B
回合開始 STARTTURN
使用卡牌A,造成結果B USE:A|EFFECT:B 每隔5秒從服務器端讀一次A的行為,改變當前戰場狀態,知道讀取到ENDTRUN消息
使用卡牌C,造成結果D USE:C|EFFECT:D
回合結束 ENDTURN
STARTTURN 回合開始
每隔5秒從服務器端讀一次B的行為,改變當前戰場狀態,知道讀取到ENDTRUN消息 USE:A|EFFECT:B 使用卡牌A,造成結果B
USE:C|EFFECT:D 使用卡牌C,造成結果D
ENDTURN 回合結束
請求分類 (3位)
游戲
新建一個游戲 新建一個游戲
加入一個游戲 加入一個游戲
認輸 認輸,退出一個游戲
等待游戲列表 獲取等待加入者游戲的列表
確認游戲啟動狀態 確認游戲是否處于啟動狀態
是否為先手 是否為先手
動作
抽牌 抽牌
回合結束 回合結束
行動 改變戰場的動作
下面這個鏈接是OneDriver上共享的設計書,有些圖形對象無法在瀏覽器中顯示,不知道能不能通過下載的方式保存到本地,然后打開。
代碼在GitHub上面,不過為了幫MongoDB的項目拉人氣,所以,將代碼放到了MongoDB的解決方案里面了。
大家下載代碼的時候,順手點個贊吧 Star 一下
https://github.com/magicdict/MagicMongoDBTool
新聞熱點
疑難解答