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