[原创]一步一步用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; }
    }

 

 

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

posted @ 2010-07-29 08:55    阅读(5386)  评论(10编辑  收藏  举报