[转载]一步一步用C#编写三国杀(二):牌堆的设计

[转载][原创]一步一步用C#编写三国杀(二):牌堆的设计 – 忘却之都 – 博客园.

前一节说到了一些基础性的定义。这一节开始将进入流程的分析。

首先,在游戏的场景建立之后,你就必须有一个牌堆。对于目前的需求来说,只要有手牌的牌堆即可;尽管后面可能还要有身份牌堆和武将牌堆,但目前只考虑手牌,即游戏牌。于是有以下定义:

/// <summary>
/// 定义牌堆的基本类型。
/// </summary>
/// <typeparam name=”T”>参数类型。</typeparam>
public abstract class CardHeap<T> : Collection<T>
{

}

定义为抽象的,是我希望能提供一些通用的方法以简化其他牌堆的设计。

对于牌堆来说,其一个重要的功能就是能够压出牌以供使用,因此定义如下:

压牌

/// <summary>
/// 从牌堆中压出指定数量的牌,这些牌将会从牌堆中移除。
/// </summary>
/// <param name=”number”>要压出的牌的数量。</param>
/// <returns>所压出的牌的数组。</returns>
public T[] Pop(int number)
{
if (number <= 0)
return new T[0];

if (Items.Count < number)
number
= Items.Count;

T[] newT = new T[number];
for (int i = 0; i < number; i++)
{
newT[i]
= Items.First();
Items.RemoveAt(
0);
}

return newT;
}

牌堆定义之后就需要定义洗牌的操作。由于我定义了从Collection<T> 继承,其内部有个IList<T>类型的Items属性,因此编写一个扩展方法,对IList<T>类型的数据进行类似洗牌的操作:

洗牌扩展

/// <summary>
/// 定义对List的扩展方法。
/// </summary>
public static class ListExtension
{
/// <summary>
/// 将IList中的元素进行洗牌操作。
/// </summary>
/// <typeparam name=”T”>类型参数。</typeparam>
/// <param name=”list”>所要洗牌的List。</param>
public static void Shuffle<T>(this IList<T> list)
{
Random random
= new Random();
int count = list.Count;

for (int i = 0; i < count; i++)
{
int currentIndex = random.Next(0, count i);
T tempCard
= list[currentIndex];
list[currentIndex]
= list[count 1 i];
list[count
1 i] = tempCard;
}
}
}

很明显,只要游戏没有结束,牌堆中拿出使用过的废牌总要回收进行循环利用,所以废牌要保存起来,并让牌堆支持其中的Items的重生。因此CardHeap中便多了如下定义:

牌堆重生

private readonly IList<T> usedItems = new List<T>();

private void ReCreate()
{
// 将usedItems的牌还原到Items中,并进行洗牌,然后清空usedItems
((List<T>) Items).AddRange(usedItems);
usedItems.Clear();
Items.Shuffle();
}

既然多了usedItems,那么上面定义的Pop就需要有个重载,以指定牌堆是否可以进行重生,所以重构上面的Pop方法,改为重载:

重载Pop

/// <summary>
/// 从牌堆中压出指定数量的牌,这些牌将会从牌堆中移除。如果牌堆的牌数量不够,则只返回牌堆中剩余的牌。
/// </summary>
/// <param name=”number”>要压出的牌的数量。</param>
/// <returns>所压出的牌的数组。</returns>
public T[] Pop(int number)
{
return Pop(number, false);
}

/// <summary>
/// 从牌堆中压出指定数量的牌,这些牌将会从牌堆中移除。
/// </summary>
/// <param name=”number”>要压出的牌的数量。</param>
/// <param name=”recreateHeap”>在压出牌数量不够的时候是否重新创建牌堆。</param>
/// <returns>所压出的牌的数组。</returns>
public T[] Pop(int number, bool recreateHeap)
{
if (number <= 0)
return new T[0];

if (Items.Count < number && !recreateHeap)
number
= Items.Count;

T[] newT = new T[number];
for (int i = 0; i < number; i++)
{
if (recreateHeap && Items.Count == 0)
{
ReCreate();
}

newT[i] = Items.First();
Items.RemoveAt(
0);
usedItems.Add(newT[i]);
}

return newT;
}

这样,牌堆的定义就算基本完成了。但在此基础上考虑考虑扩展包的问题。实际上扩展包主要是牌的扩展,而且在设计初期,就已经考虑将标准版的牌从这个Core中分离,即除了基本牌的杀、闪、桃之外,锦囊和装备、武将都是由扩展包来提供。因此定义了个扩展包的接口:

扩展包接口

/// <summary>
/// 定义扩展包所必须实现的接口。
/// </summary>
public interface IPackage
{
/// <summary>
/// 扩展包中的游戏牌。
/// </summary>
GameCard[] GameCards { get; }
}

好,牌堆的设计就说到这里,后面就定义实际的基本牌,并将进入实际流程循环。

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏