不知道大家有沒有玩過一個叫《拖板車》的撲克遊戲(貌似暴露了年齡。。),本篇我們嘗試讓計算機來玩這個遊戲,並且我們在確定雙方的牌和順序後就能立馬知道誰將是贏家。在玩這個遊戲前我們需要先了解下隊列和棧。
隊列簡介
隊列是一種特殊的線性結構,它只允許在隊列的首部進行刪除操作,這稱作“出隊”,而在隊列的尾部進行插入操作,這稱爲“入隊"。這和我們實際生活中排隊買東西的場景一樣,先買東西的肯定是隊首的第一個,新來的人肯定是排隊在最後面。我們稱之爲先進先出(FIFO)原則。
隊列實現
隊列的數據結構實現起來比較簡單,用一個數組控制先進先出即可,.net平臺已經內置了這種數據結構,我們一起看一下萬能的微軟是如何實現隊列的。代碼較多,我們看一下關鍵部分入隊和出隊的代碼。
private T[] _array;
private int _head;
private int _tail;
private int _size;
private int _version;
//出隊
public T Dequeue()
{
//判斷隊列中是否有元素,如果沒有拋出空隊列異常
if (_size == 0)
{
System.ThrowHelper.ThrowInvalidOperationException(System.ExceptionResource.InvalidOperation_EmptyQueue);
}
//取得隊首元素,作爲返回結果
T result = _array[_head];
//將隊首元素重置爲默認值
_array[_head] = default(T);
//_head指向隊首下一個元素
_head = (_head + 1) % _array.Length;
//隊列長度減一
_size--;
//版本更新
_version++;
return result;
}
//入隊
public void Enqueue(T item)
{
//.net在初始化隊列的時候會給數組一個初始長度,當數組數量達到一定條件會擴容,每次增加4的長度
if (_size == _array.Length)
{
int num = (int)((long)_array.Length * 200L / 100);
if (num < _array.Length + 4)
{
num = _array.Length + 4;
}
SetCapacity(num);
}
//將新元素加到數組末尾
_array[_tail] = item;
//_tail指向末尾後一個索引
_tail = (_tail + 1) % _array.Length;
//隊列長度+1
_size++;
_version++;
}
棧簡介
棧和隊列類似,也是一種特殊的線性結構,同樣在尾部進行插入操作,但是刪除元素同樣是在隊尾,採用先進後出原則。假如我們往一個直徑只有乒乓球大小的水杯分別放入2,3,1號乒乓球,那麼我們想要拿出2號球就必須先分別拿出1號球和3號球,這裏的水杯就是典型的棧結構。同樣地我們看下微軟關於棧的關鍵代碼實現。
private T[] _array;
private int _size;
private int _version;
//出棧
public T Pop()
{
//判斷棧中是否還有元素可以出棧,沒有則拋出空棧異常
if (_size == 0)
{
System.ThrowHelper.ThrowInvalidOperationException(System.ExceptionResource.InvalidOperation_EmptyStack);
}
//版本+1
_version++;
//取的數組尾部元素作爲返回結果,並且長度記錄-1
T result = _array[--_size];
//將原來的尾部元素重置爲默認值
_array[_size] = default(T);
return result;
}
//入棧
public void Push(T item)
{
//同隊列相似,如果數組容量達到條件就擴容
if (_size == _array.Length)
{
T[] array = new T[(_array.Length == 0) ? 4 : (2 * _array.Length)];
Array.Copy(_array, 0, array, 0, _size);
_array = array;
}
//將新元素添加到數組尾部同時長度記錄+1
_array[_size++] = item;
_version++;
}
拖板車遊戲
遊戲玩法
將一副撲克牌隨機分爲兩份,小明和小紅各一份。小明先將手中的第一張撲克牌放在桌上,然後小紅也拿出手中的第一張牌放在小明剛打出的撲克牌上面,就像這樣兩人交替出牌。出牌時,如果某人打出的牌和桌上某張牌數字相同,即可將這兩張牌及中間所夾的牌全部收走並放到自己手牌的末尾。當其中任意一人手中牌爲空的時候,遊戲結束,對手獲勝。
我們分析下這個遊戲過程,兩個人的操作都是一樣的,手裏的牌相當於一個隊列,出牌是出隊,贏牌是入隊。而桌上的牌相當於一個棧,出牌的時候是入棧,贏牌的時候是出棧。這樣我們的實現思路就清晰了,下面我們用程序來模擬這個遊戲。
編碼實現
static void Main(string[] args)
{
var all = new List<string>();
//首先將52張牌放入集合
for (int i = 0; i < 4; i++) //每種牌四張,循環四次
{
for (int j = 2; j < 15; j++) //13種牌【2,3,4,5,6,7,8,9,10,J,Q,K,A】
{
if (j <= 10)
{
all.Add(j.ToString());
}
else
{
switch (j)
{
case 11:
all.Add("J");
break;
case 12:
all.Add("Q");
break;
case 13:
all.Add("K");
break;
case 14:
all.Add("A");
break;
default:
break;
}
}
}
}
//然後將54張牌隨機分到小明和小紅手裏
//定義兩個隊列用來記錄小明和小紅的手牌
Queue<string> xiaoming = new Queue<string>();
Queue<string> xiaohong = new Queue<string>();
var strming = "小明手上的牌依次爲:";
var strhong = "小紅手上的牌依次爲:";
for (int i = 0; i < 52; i++)
{
var random = new Random().Next(0, 51 - i);
if (i%2==0)
{
xiaoming.Enqueue(all[random]);
strming += all[random]+" ";
}
else
{
xiaohong.Enqueue(all[random]);
strhong += all[random] + " ";
}
all.RemoveAt(random);
}
Console.WriteLine(strming);
Console.WriteLine(strhong);
//開始遊戲
//先定義一個棧來記錄桌面上的牌
var stack = new Stack<string>();
while (xiaoming.Count>0&&xiaohong.Count>0)
{
var first = xiaoming.Dequeue(); //小明出牌
Console.WriteLine("小明出牌:"+first);
//判斷桌面上是否有剛出的牌
if (stack.Contains(first))
{
//如果有那這兩張牌及中間的牌都入隊小明
xiaoming.Enqueue(first);
while (stack.First()!=first)
{
xiaoming.Enqueue(stack.Pop());
}
xiaoming.Enqueue(stack.Pop());
Console.Write("小明贏得一輪,桌上剩餘牌爲:");
foreach (var item in stack)
{
Console.Write(item + " ");
}
Console.WriteLine("");
}
else
{
//沒有的話放到桌面上
stack.Push(first);
}
first = xiaohong.Dequeue(); //小紅出牌
Console.WriteLine("小紅出牌:" + first);
//判斷桌面上是否有剛出的牌
if (stack.Contains(first))
{
//如果有那這兩張牌及中間的牌都入隊小紅
xiaohong.Enqueue(first);
while (stack.First() != first)
{
xiaohong.Enqueue(stack.Pop());
}
xiaohong.Enqueue(stack.Pop());
Console.Write("小紅贏得一輪,桌上剩餘牌爲:");
foreach (var item in stack)
{
Console.Write(item + " ");
}
Console.WriteLine("");
}
else
{
//沒有的話放到桌面上
stack.Push(first);
}
}
if (xiaoming.Count==0)
{
Console.WriteLine("小紅獲勝!");
}
else
{
Console.WriteLine("小明獲勝!");
}
Console.ReadKey();
}
來看一下執行結果,輸出較多,只截了第一張和最後一張:
有趣的拖板車就到此爲止啦~
歡迎關注我的算法系列博客:
算法(https://blog.csdn.net/leaderxin/article/category/9434286)