前幾天與幾位從事線上棋牌遊戲開發的朋友聚會,話題自然落在洗牌算法上。因為早年開發過多款撲克遊戲,手邊正好有一個現成的洗牌實作,便拿出來與大家分享驗證。這個算法的核心思路非常直觀——採用隨機交換法:將 54 張牌依序排列,隨機抽取兩張進行交換。理論上,交換次數若達到牌數(54 次),牌序已極度混亂;若求更保險,加倍至 108 次即可滿足實戰需求。
完整程式碼結構清晰,包含牌面定義、初始化邏輯、洗牌核心(Wash)、取牌介面(Get)以及除錯輸出(PrintInfo)。關鍵在於 Random() 函式:每次呼叫產生兩個 0~53 的隨機索引,若撞號則將第二個索引位移一位(邊界回繞),隨後呼叫 Exchange() 完成結構體級別的記憶體交換。
// 關鍵片段:隨機交換邏輯
void Random(void) {
int a = GetRandomBetween(0, POKER_MAX);
int b = GetRandomBetween(0, POKER_MAX);
if (a == b) {
b = a + 1;
if (POKER_MAX <= b) b = 0;
}
Exchange(a, b);
}
// 預設洗 54 次,呼叫端可自行增加次數
void Wash(int nTime = POKER_MAX) {
for (int i = 0; i < nTime; ++i) Random();
}
原版在多線程環境下存在競態風險(一邊洗牌、一邊取牌會導致資料損壞)。參考《0bug-C/C++商用工程之道》中的「資源鎖」模式,以私有聚合方式封裝 CPoker,所有公有方法加鎖,實現線程安全:
class CPokerWithLock {
public:
void Wash(int nTime = POKER_MAX) {
m_Lock.Lock();
m_Poker.Wash(nTime);
m_Lock.Unlock();
}
bool Get(unsigned int nIndex, SCard* pCard) {
bool bRet = false;
m_Lock.Lock();
bRet = m_Poker.Get(nIndex, pCard);
m_Lock.Unlock();
return bRet;
}
private:
CPoker m_Poker; // 被保護的資源
CMutexLock m_Lock; // 跨平台安全鎖(書中 6.2 節實作)
};
測試代碼演示初始序列、洗牌後序列、再次洗牌序列的變化。若無書本配套鎖類別,直接將 CPokerWithLock Poker; 改為 CPoker Poker; 即可使用非線程安全版本。全程在 VS2008 下編譯通過,邏輯驗證無誤。
這個洗牌算法雖非 Fisher-Yates 標準演算法,但以「雙重隨機選位交換」模擬真人洗牌的非確定性特徵,工程上簡單有效。線程安全封裝展示了資源鎖模式在實際專案中的輕量級應用。歡迎各位同好交流指正。