最近在大连的同事强力推荐我玩 炉石传说,一个卡牌游戏。加上五一放一个很长很长的假期,为了磨练自己,决定尝试开发一个C#的炉石传说。

这件事情有人已经干过了,开发了一个网页版的炉石,但是貌似不能玩。。。。

http://cnodejs.org/topic/529c1366a6957a0809485f3d

如果这位同志看到这篇文章,请一定和我联系!!

[email protected]或Q我377372779

第一天

开始学习炉石传说的玩法,最好的方法是不停的玩游戏。

一个应用是否逻辑清晰,取决于你对于业务的了解程度,一般到开发后期发现有些逻辑内聚和耦合发生问题,往往都是前期对于业务的理解不够透彻。

很多开发都往往是随着业务逻辑的了解,进行不停的重构,当然,这个也是一个必然的过程,但是如果能够在前期就了解业务的话,则可以节约后期大量的时间。

由于长期做对日软件的缘故,式样书,设计书先行是根深蒂固的思想。所有设想都先以文字或者伪代码的形式写下来,进行一些假想的验证。

整个项目的平衡感,脉络,各个模块,层次结构都在这个时候定下来。这个时候是修改成本最低的阶段,等到后期这些模块再重新划分,风险就高了。

通过第一天的学(you)习(xi),大概整理除了一个脉络:

(文字版本的不是很好看,下面的Excel版本,浏览器也看不到。。)

炉石传说 C# 开发笔记-LMLPHP

卡牌基础

  法术卡牌

    熟读各种法术牌,讲法术牌分类

  随从卡牌

    各种特性的整理,可以参考各种网络上的资料

  武器卡牌

    比较简单的类型

游戏环境

  英雄

    生命值

    基本技能

    武器

  牌堆

    套牌

  手牌

    手里的牌  

  战场

    7个位置的随从

  法力水晶

    由于某些卡牌会改变水晶,也为了细化系统,法力水晶升级为一个独立的类

第二天

  开始进行Coding。由于英语不是很好,有一些单词不知道,然后开始中英文夹杂编码。

  很久前,也讨论过中文编程的问题,其实很多变量,用中文还是英语完全没有限制。

  写代码只要能让自己和维护的人读得懂就可以了。毕竟即使你用英语变量,你的注释还是中文的。。。

  当然,如果你想让代码能够国际化,特别是开源项目,能用标准的英语来写代码是极好的。

  NUnit用的不是很好,所以,自己写了一些GUI的界面来做一些简单的UT测试。

炉石传说 C# 开发笔记-LMLPHP

第三天

  代码的重构,设计书和代码的同步。

  很多项目,在一开始的时候还有设计书,然后在开发的时候,往往重构好代码后,设计书还是重构之前的样子。

  IDE可以自动重构代码,但是不能自动重构设计书。。。。

  国内项目不注重文档,所以这种情况很常见。日系的开发,设计书则相当重要,一个是为了日后维护能有个依据,二是为了能够明确责任。

  这个地方为什么要修改,对于整体项目有什么影响,都能从设计书的修改履历中看出端倪。

  代码和设计书同步的时候,也是一个反思的机会,看看现阶段写的代码,是不是很干净优雅,

往往将代码转换为设计书的时候,可以看到代码的问题。特别是代码的一致性上,散落在不同地方的代码,经过整理,用#region归纳后,可以看到很多问题。

炉石传说 C# 开发笔记-LMLPHP

第四天

  炉石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 = ;
奥术智慧.StandardCostPoint = ;
奥术智慧.JoinType = Card.MagicCard.EffectJoinType.None; //随机抽两张牌
Card.MagicCardStockEffect cardStockEffect = new Card.MagicCardStockEffect();
cardStockEffect.StandardEffectPoint = ;
cardStockEffect.EffectCount = ;
cardStockEffect.EffectTargetSelectDirect = Card.CardUtility.TargetSelectDirectEnum.本方;
奥术智慧.FirstMagicDefine = cardStockEffect;
return 奥术智慧;
}

第五天

 我一直在考虑,AI是不是能代替人。

  炉石这样的游戏,有许多常用的套路,只要组好了套牌,然后能够将很多常用的卡牌组合,优先策略教授给AI,应该可以做到和人对战。

  和国际象棋,围棋比起来,炉石这样的游戏,胜利无非是:运气好,套牌组的合理,正确衡量场面上各种对方卡牌的威胁程度,熟练使用各种套路,有耐心,不犯低级错误。

  运气好,套牌组的合理,这个事情,前者人和AI都一样,套牌可以人组好后直接给AI使用。

  正确衡量场面上各种对方卡牌的威胁程度:这个也不难,其实卡牌的使用成本已经是一个可以量化的威胁度指标了。

  熟练使用各种套路:对方出了一个 10/10 (合理的阀值)的家伙,如果有变羊术,就变掉;对手一大堆血量3,4的随从,就用清场的牌,这些套路也很直观

  有耐心,不犯低级错误:这个是AI的长处,AI绝对不会忘记还有魔法可以直接 打脸,还有可以使用的随从去 打脸

  当然,对于顶级高手AI还不是可以简单的取胜,审时度势,及时调整战略的能力,人还是有着无可比拟的优势。

第六天

  看看客户端和服务器端分工如何:

  顺便提一句,日常文档的编写,WPS不比Office差,支持国货

炉石传说 C# 开发笔记-LMLPHP

  核心库 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 = ; i < CardCount; i++)
{
if (CardList.Count == ) 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上共享的设计书,有些图形对象无法在浏览器中显示,不知道能不能通过下载的方式保存到本地,然后打开。

点击这里查看 onlne Excel 版本的 设计书

代码在GitHub上面,不过为了帮MongoDB的项目拉人气,所以,将代码放到了MongoDB的解决方案里面了。

大家下载代码的时候,顺手点个赞吧 Star 一下

https://github.com/magicdict/MagicMongoDBTool

04-16 18:59