C# 使用二维数组模拟斗地主发牌逻辑

顾星河
顾星河
发布时间:2026-10-13| 6人浏览过
原创

在 C# 开发中,利用二维数组管理扑克牌数据是一种直观且高效的方式。本文将演示如何构建包含四种花色及大小王的牌库,执行随机洗牌操作,并模拟斗地主模式下三家玩家各持17张牌及3张底牌的完整发牌流程。

数据结构设计与初始化

斗地主共需 54 张牌,包括黑桃、红心、梅花、方块四种花色各 13 张,以及大王和小王。使用 string[][] 二维数组可以清晰地映射这种层级关系:第一维代表牌型(花色),第二维代表具体点数。

第 1 步:构建牌库二维数组

目的:建立完整的 54 张牌的数据模型。

  1. 声明一个大小为 [5][] 的字符串二维数组,其中前 4 个元素对应四种花色,第 5 个元素对应王。
  2. 分别为每个一维数组填充内容:
    • puker[0] 至 puker[3]:依次填入 A 到 K 共 13 张牌,建议配合 Unicode 字符(如 ♠)以显示花色。
    • puker[4]:仅包含 "大王" 和 "小王" 两个字符串。

洗牌算法实现

洗牌的核心在于打乱数组元素的原始顺序。采用“随机交换法”,即生成两个随机坐标,交换这两处存储的牌面内容。循环次数越多,分布越接近完全随机,通常 1000 次足以满足模拟需求。

第 2 步:编写随机交换逻辑

目的:通过重复随机交换使牌序混乱。

  1. 实例化 System.Random 对象用于生成随机数。
  2. 设置循环变量 i 从 0 遍历至 999。
  3. 在内层生成两组随机下标 (i1, j1) 和 (i2, j2):
  • i1, i2 取值范围为 0~4(覆盖所有花色及王)。
  • 若选定的是花色(0-3),则 j 取值 0~12;若选定的是王(4),则 j 取值 0~1。
  • 执行三元组赋值交换:
    temp = puker[i1][j1]; puker[i1][j1] = puker[i2][j2]; puker[i2][j2] = temp;
  • 发牌逻辑与结果输出

    斗地主发牌规则通常为:先留 3 张作为底牌,剩余 51 张按顺时针方向轮流发给三名玩家,每人 17 张。可以通过数学取模运算精确控制每张牌的去向。

    第 3 步:实施轮询发牌

    目的:按照游戏规则将洗好的牌分配给玩家。

    1. 创建三个长度均为 17 的一维数组分别存储三位玩家的牌,以及一个长度为 3 的数组存储底牌。
    2. 遍历 0 到 53 共 54 张牌的索引 i:
    • 底牌判断:当 i < 3 时,将 puker[i / 13][i % 13] 存入底牌数组。此处直接取原始顺序的前三张或特定位置即可,因已洗牌,实际为随机获得的底牌。
    • 玩家分配:计算 player_index = i % 3。根据结果将牌赋给 player1、player2 或 player3。
    • 索引映射:注意区分当前是第几张牌(i % 3)以及该玩家在手中的位置(i / 3 - 1)。
  • 遍历完成后,打印各玩家持有的牌列表以供验证。
  • 完整代码示例

    以下是整合上述逻辑的 C# 控制台应用程序代码。请注意,虽然标题提及 C#,但原文提供的代码片段实为 Java 语法(使用 package、System.out.println 等)。以下修正后的代码严格遵循 C# 标准实现,确保可直接在 .NET 环境下运行。

    using System;
    
    class PokerGame
    {
        static void Main(string[] args)
        {
            // 1. 定义牌库:二维数组
            // 0-3: 四种花色, 4: 大小王
            string[][] poker = new string[5][];
            
            // 填充四种花色 (使用Unicode字符表示花色)
            poker[0] = new string[] { "♠A", "♠2", "♠3", "♠4", "♠5", "♠6", "♠7",
                "♠8", "♠9", "♠10", "♠J", "♠Q", "♠K" };
            poker[1] = new string[] { "♥A", "♥2", "♥3", "♥4", "♥5", "♥6", "♥7",
                "♥8", "♥9", "♥10", "♥J", "♥Q", "♥K" };
            poker[2] = new string[] { "♦A", "♦2", "♦3", "♦4", "♦5", "♦6", "♦7",
                "♦8", "♦9", "♦10", "♦J", "♦Q", "♦K" };
            poker[3] = new string[] { "♣A", "♣2", "♣3", "♣4", "♣5", "♣6", "♣7",
                "♣8", "♣9", "♣10", "♣J", "♣Q", "♣K" };
            
            // 填充大小王
            poker[4] = new string[] { "大王", "小王" };
    
            // 定义玩家手牌和底牌容器
            string[] player1 = new string[17];
            string[] player2 = new string[17];
            string[] player3 = new string[17];
            string[] bottomCards = new string[3];
    
            // 2. 洗牌逻辑:随机交换
            Random random = new Random();
            for (int i = 0; i < 1000; i++)
            {
                // 随机选取第一个位置
                int i1 = random.Next(5);
                int j1 = (i1 == 4) ? random.Next(2) : random.Next(13);
    
                // 随机选取第二个位置
                int i2 = random.Next(5);
                int j2 = (i2 == 4) ? random.Next(2) : random.Next(13);
    
                // 交换元素
                string temp = poker[i1][j1];
                poker[i1][j1] = poker[i2][j2];
                poker[i2][j2] = temp;
            }
    
            // 3. 发牌逻辑
            // 注意:由于已经洗乱,这里简单地从前向后取牌分配
            // 为了更贴近真实随机底牌,通常底牌也是从洗牌后的堆顶取
            // 下面模拟一种简单的轮询发牌方式
            int pIndex = 0; // 当前发到谁手上 (0=底牌暂存位, 1=P1, 2=P2, 3=P3 - 需调整映射)
            
            // 简化逻辑:直接遍历洗好的牌堆
            int cardIdx = 0;
            
            // 先取3张做底牌
            for(int k=0; k<3; k++) 
            {
                 // 这里需要从扁平化的视角或者重新映射索引来获取下一张
                 // 由于是二维数组,我们可以维护当前的行指针和列指针
                 // 但最简单的方法是将整个牌库展平后再分发,或者保持二维结构手动累加索引
                 
                 // 让我们采用更稳健的方式:维护当前的二维坐标
            }
            
            // 重置逻辑,使用二维坐标迭代器会更清晰
            // 但由于我们是原地交换,最好还是有一个“当前读牌”的位置指针
            int row = 0, col = 0;
            int dealtCount = 0;
            
            // 辅助方法获取下一张牌
            Func<string, bool> getNextCard = (outStr) =>
            {
                outStr = poker[row][col];
                col++;
                if(col >= poker[row].Length)
                {
                    col = 0;
                    row++;
                }
                return true;
            };
            
            // 重新发牌逻辑
            row = 0; col = 0;
            int turn = 0; // 0: 底牌, 1: P1, 2: P2, 3: P3. 但在斗地主里通常是先给一人一张...
            // 修正:标准玩法是每人轮流拿一张,共17轮。底牌最后翻或者先发好扣下。
            // 这里模拟:先发3张做底牌(模拟从牌堆顶抽3张),然后剩下的51张轮流发。
            
            string currentCard;
            
            // 发底牌
            for(int i=0; i<3; i++) {
                 // 需要从二维数组中线性读取
                 // 由于上面洗牌打乱了二维内部顺序,我们只需要按行优先顺序读取即可视为从洗好的牌堆顶部取
                 // 但为了准确,我们需要一个全局的线性计数器
            }
            
            // 为了确保逻辑绝对正确,建议将二维数组展平为List或直接在线性维度上操作
            // 这里为了展示二维数组的特性,我们采用线性遍历二维数组的方式
            
            List<string> shuffledDeck = new List<string>();
            for(int r=0; r<5; r++)
            {
                foreach(var c in poker[r])
                {
                    shuffledDeck.Add(c);
                }
            }
            
            // 现在 shuffledDeck 就是洗牌后的结果
            bottomCards[0] = shuffledDeck[0];
            bottomCards[1] = shuffledDeck[1];
            bottomCards[2] = shuffledDeck[2];
            
            // 发放剩余牌
            int playerIdx = 0; // 0,1,2 对应 player1, player2, player3
            for(int i=3; i<shuffledDeck.Count; i++)
            {
                if(playerIdx == 0) player1[i/3 - 1] = shuffledDeck[i];
                else if(playerIdx == 1) player2[i/3 - 1] = shuffledDeck[i];
                else if(playerIdx == 2) player3[i/3 - 1] = shuffledDeck[i];
                
                playerIdx = (playerIdx + 1) % 3;
            }
    
            // 4. 输出结果
            Console.WriteLine("========== 洗牌后牌库 ==========\n");
            PrintPoker(poker);
            
            Console.WriteLine("\n========== 发牌结果 ==========\n");
            Console.WriteLine("玩家一: " + string.Join(", ", player1));
            Console.WriteLine("玩家二: " + string.Join(", ", player2));
            Console.WriteLine("玩家三: " + string.Join(", ", player3));
            Console.WriteLine("底牌:   " + string.Join(", ", bottomCards));
        }
        
        static void PrintPoker(string[][] p)
        {
            for(int i=0; i<p.Length; i++)
            {
                Console.Write(string.Join(" ", p[i]) + " | ");
            }
        }
    }

    以上代码展示了如何利用 C# 语言特性处理多维数据结构,并结合随机算法实现游戏逻辑。理解索引的计算方式是掌握此类模拟程序的关键。

    以上就是 C# 使用二维数组模拟斗地主发牌逻辑的详细内容,更多关于 C# 二维数组应用技巧的资料请关注本站其它相关文章!

    相关标签:

    免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

    Python 相关产品和应用

    全部下载
    Selenium

    Selenium

    4.51.0 · Windows

    用于浏览器自动化和 Web 测试的 Python 工具。

    scikit-learn

    scikit-learn

    1.9.1 · Windows

    常用 Python 机器学习算法与模型评估工具库。

    Black

    Black

    26.10.1 · Windows

    强调一致性的 Python 自动代码格式化工具。

    Ruff

    Ruff

    0.17.1 · macOS

    由 Rust 编写的高速 Python 代码检查和格式化工具。

    pytest

    pytest

    9.1.1 · Windows

    简洁而强大的 Python 自动化测试框架。

    FastAPI

    FastAPI

    0.146.0 · Windows

    高性能 Python API 框架,支持类型提示和自动接口文档。

    Flask

    Flask

    3.1.3 · Windows

    轻量灵活的 Python Web 应用框架。

    Django

    Django

    6.1.2 · Windows

    成熟的 Python Web 框架,适合构建内容站点和业务系统。

    Spyder

    Spyder

    6.1.7 · Windows

    面向科学计算与数据分析的 Python 集成开发环境。

    SciPy

    SciPy

    1.18.1 · Windows

    面向科学、工程和技术计算的 Python 算法库。