最近在大连的同事强力推荐我玩 炉石传说,一个卡牌游戏。加上五一放一个很长很长的假期,为了磨练自己,决定尝试开发一个C#的炉石传说。
这件事情有人已经干过了,开发了一个网页版的炉石,但是貌似不能玩。。。。
http://cnodejs.org/topic/529c1366a6957a0809485f3d
如果这位同志看到这篇文章,请一定和我联系!!
[email protected]或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 = ;
奥术智慧.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差,支持国货
核心库 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上共享的设计书,有些图形对象无法在浏览器中显示,不知道能不能通过下载的方式保存到本地,然后打开。
代码在GitHub上面,不过为了帮MongoDB的项目拉人气,所以,将代码放到了MongoDB的解决方案里面了。
大家下载代码的时候,顺手点个赞吧 Star 一下