Lua 和 Love 2d 教程 二十一点朴克牌 (上篇lua源码)

时间:2024-04-03 18:54:44

GitCode - 开发者的代码家园 Lua版完整原码

规则

庄家和玩家各发两张牌。庄家的第一张牌对玩家是隐藏的。

玩家可以拿牌(即拿另一张牌)或 停牌(即停止拿牌)。

如果玩家手牌的总价值超过 21,那么他们就爆掉了。

面牌(国王K、皇后Q 和 大臣J)的值为 10,而 A 的值为 11,除非这会使手牌的总值超过 21,在这种情况下,它们的值为 1。

在玩家停牌或爆掉后,庄家拿牌,直到他们的手牌总数达到 17 或以上。

然后回合结束,总分最高的手牌(如果总分为 21 或以下)赢得回合。

控制

左键单击 点击拿牌或停牌按钮

概述

每张牌都由一个表格表示,该表格包含一个代表其花色的字符串和一个代表其等级的数字。大臣、皇后和国王由数字 11、12 和 13 表示。

一副牌由一个表格表示,该表格最初包含每张牌中的一张。

['梅花'] = {1,2,3,4,5,6,7,8,9,10,11,12,13}, ['方块'] = {1,2,3,4,5,6,7,8,9,10,11,12,13}, ['红桃'] = {1,2,3,4,5,6,7,8,9,10,11,12,13}, ['黑桃'] = {1,2,3,4,5,6,7,8,9,10,11,12,13}

编码

一副纸牌

每张牌都由一个表格表示,该表格包含一个代表其花色的字符串和一个代表其等级的数字。大臣、皇后和国王由数字 11、12 和 13 表示。

为每张牌创建一个包含一张牌的牌组表。

花色: 梅花, 点数: 1
花色: 梅花, 点数: 2
花色: 梅花, 点数: 3
花色: 梅花, 点数: 4
花色: 梅花, 点数: 5
花色: 梅花, 点数: 6
花色: 梅花, 点数: 7
花色: 梅花, 点数: 8
花色: 梅花, 点数: 9
花色: 梅花, 点数: 10
花色: 梅花, 点数: 11
花色: 梅花, 点数: 12
花色: 梅花, 点数: 13
花色: 方块, 点数: 1
花色: 方块, 点数: 2
花色: 方块, 点数: 3
花色: 方块, 点数: 4
花色: 方块, 点数: 5
花色: 方块, 点数: 6
花色: 方块, 点数: 7
花色: 方块, 点数: 8
花色: 方块, 点数: 9
花色: 方块, 点数: 10
花色: 方块, 点数: 11
花色: 方块, 点数: 12
花色: 方块, 点数: 13
花色: 红桃, 点数: 1
花色: 红桃, 点数: 2
花色: 红桃, 点数: 3
花色: 红桃, 点数: 4
花色: 红桃, 点数: 5
花色: 红桃, 点数: 6
花色: 红桃, 点数: 7
花色: 红桃, 点数: 8
花色: 红桃, 点数: 9
花色: 红桃, 点数: 10
花色: 红桃, 点数: 11
花色: 红桃, 点数: 12
花色: 红桃, 点数: 13
花色: 黑桃, 点数: 1
花色: 黑桃, 点数: 2
花色: 黑桃, 点数: 3
花色: 黑桃, 点数: 4
花色: 黑桃, 点数: 5
花色: 黑桃, 点数: 6
花色: 黑桃, 点数: 7
花色: 黑桃, 点数: 8
花色: 黑桃, 点数: 9
花色: 黑桃, 点数: 10
花色: 黑桃, 点数: 11
花色: 黑桃, 点数: 12
花色: 黑桃, 点数: 13
桌面所有的牌张数: 52

 玩家和庄家的手牌由表格表示,牌在随机位置从牌组中取出,并在他们拿牌时插入他们的手中。

建立一个名为“二十一点”的类,用以初始化游戏对象


   - 定义了一个名为`二十一点`的游戏类,其中包含了一些成员变量,如牌叠、玩家手牌数组(包含闲家和庄家)、当前玩家标识、点数(闲家点数、庄家点数)等。
   - 类中实现了多个方法:
     - `新`方法用于初始化一个新的游戏对象,并调用`洗牌`方法准备游戏。
     - `洗牌`方法首先清空牌叠,然后构建一副新的扑克牌,并对其进行随机化处理。
     - `发牌`方法负责从牌叠中取出一张牌分配给指定玩家,并可以选择是否显示这张牌的内容。
     - `计算点数`方法计算玩家手中的牌面总点数,注意A在这里被特殊处理,既可视为1也可视为11点。

-- 使用模块化方法定义游戏类
局部 二十一点 = {}
二十一点.__index = 二十一点

-- 初始化游戏对象
函数 二十一点.新()
    局部 游戏 = 设元({
        牌叠 = {},       -- 一副新朴克牌
        玩家 = {{}, {}}, -- 闲家和庄家
        当前玩家 = 1, -- 当前玩家
        闲家点数 = 0, -- 闲家点数
        庄家点数 = 0, -- 庄家点数
        输入值 = "",
    }, 二十一点)
    游戏:洗牌() -- 初始化洗牌
    返回 游戏
结束

首先、洗牌

在上述代码片段中,`二十一点:洗牌()` 函数是用来初始化一副新的扑克牌并进行洗牌操作的。以下是对此函数步骤的详细说明:

1. **初始化牌叠**:


    首先清空游戏对象(`self`)中的牌叠属性,将其设置为空表 `{}`。

2. **构建扑克牌集合**:


   - 使用一个循环遍历一个字典结构,该结构代表四种花色及其对应的序数(1到13,分别对应扑克牌的A到K)。这里的“匹配”可能是指 Lua 中的一种迭代机制,假设它能遍历键值对。
   - 对于每一种花色('梅花', '方块', '红桃', '黑桃'),其对应的序数列表会被逐个遍历。
   - 再次使用内部循环,通过"_ , 点数"遍历序数列表,将序数赋值给变量`点数`。

3. **添加扑克牌到牌叠**:


   - 对每个花色和点数组合,向`self.牌叠`表中插入一个新的记录,这个记录是一个包含花色和点数两个字段的表(Lua 中的哈希表)。

4. **初始化随机数种子**:


   - 使用系统当前时间作为随机数生成器的种子,确保每次洗牌时都能得到不同的随机序列。

5. **随机化牌叠**:

6. 最后调用`随机化数组`函数,

    传入刚刚构建好的牌叠`self.牌叠`,对整副牌进行随机排序,完成洗牌操作。

总结来说,这个函数完成了构建一副完整的扑克牌集合,并对其进行随机排列的过程,为接下来的游戏发牌阶段做准备。

-- 洗牌
函数 二十一点:洗牌()
    self.牌叠 = {}
    因为 花色, 序数 属于 匹配({['梅花'] = {1,2,3,4,5,6,7,8,9,10,11,12,13}, ['方块'] = {1,2,3,4,5,6,7,8,9,10,11,12,13}, ['红桃'] = {1,2,3,4,5,6,7,8,9,10,11,12,13}, ['黑桃'] = {1,2,3,4,5,6,7,8,9,10,11,12,13}}) 做
        因为 _, 点数 属于 序配(序数) 做
            表.insert(self.牌叠, {花色 = 花色, 点数 = 点数})
        结束
    结束
    数.randomseed(系统.time()) -- 初始化随机数生成器
    随机化数组(self.牌叠) -- 使用函数随机化表
结束
`随机化数组`函数,

这个函数 `随机化数组(t)` 是用来对输入的数组或表 `t` 进行原地随机排序的,通常也被称为 Fisher-Yates shuffle 或 Knuth shuffle。函数的工作原理如下:

1. 遍历数组或表 `t` 的索引,从最后一个元素开始向前遍历(即从 `i = #t` 到 `i = 2`,步长为 `-1`)。
2. 对于当前索引 `i`,生成一个随机索引 `j`,满足 `1 <= j <= i`。
3. 交换 `t[i]` 和 `t[j]` 的值,这样就将原本位于索引 `i` 处的元素移动到了一个随机位置。
4. 继续处理下一个索引,直到第一个元素(索引为 1)为止。

通过这样的过程,整个数组或表的顺序就被随机打乱了,实现了一个均匀随机分布的洗牌效果。这个算法保证了每一个元素都有均等的概率出现在任何一个位置上。

-- 定义一个用于随机化数组的函数
函数 随机化数组(t)
    因为 i = #t, 2, -1 做
        局部 j = 数.random(i)
        t[i], t[j] = t[j], t[i]
    结束
结束

第二、发牌

这段伪代码定义了一个名为“二十一点:发牌”的函数,该函数在二十一点游戏中负责给玩家发牌的操作。函数的主要逻辑如下:

- `局部 手牌 = 表.remove(self.牌叠, 1)`:从游戏状态中的公共牌堆(`self.牌叠`)中移除并返回最上面的一张牌作为玩家的新手牌。`表.remove`操作会改变原始牌堆的结构,确保每次只发一张且不重复。

- `表.insert(玩家, 手牌)`:将刚才从牌堆取出的那张手牌插入到指定玩家的手牌列表中。

- `如果 明牌 即`:检查是否需要公开显示此手牌(即明牌)。

- `输出((玩家 == self.玩家[1] 与 "闲家" 或 "庄家").."手牌 " .. 手牌.花色 ..', '.. 手牌.点数)`:如果玩家需要公开手牌,则根据玩家身份(假设`self.玩家[1]`代表闲家,否则self.玩家[2]可能是庄家或其他玩家标识)输出相应的消息,包含玩家类型、手牌的花色和点数。

总结起来,这个函数实现了从公共牌堆中抽取一张牌分配给玩家,并在需要时显示这张牌的信息。在实际应用中,它会在每轮游戏开始或者玩家请求补牌时被调用。

-- 发牌
函数 二十一点:发牌(玩家, 明牌)
    局部 手牌 = 表.remove(self.牌叠, 1)
    表.insert(玩家, 手牌)
    如果 明牌 即
        输出((玩家 == self.玩家[1] 与 "闲家" 或 "庄家").."手牌 " .. 手牌.花色 ..', '.. 手牌.点数)
    结束
结束

闲家手牌 梅花, 11
闲家手牌 黑桃, 13
庄家手牌 梅花, 1

第三、计算点数

这段伪代码描述了一个名为“二十一点:计算点数”的函数,用于计算玩家手中所有牌的总点数。在二十一点游戏中,每个玩家手牌的点数之和不能超过21点。函数逻辑分解如下:

1. 初始化两个局部变量:

`合计` 设置为0,用于累计玩家手牌的点数总和。
`尖儿` 设置为假(False),用于标记玩家手中是否有A(Ace)牌且未确定其点数是1还是11。

2. 遍历玩家手中的每张牌(通过`序配(玩家)`得到一个序列):

对于每张牌,如果牌的点数大于10(通常表示是J、Q、K),则将其视为10点加到合计中。
若牌的点数不是大于10,则直接将其点数加到合计上。
如果当前牌的点数等于1(即A牌),则设置`尖儿`为真(True),表示有可以计为1或11点的A牌。

3. 检查是否有A牌并且当前合计点数小于12:

如果满足条件,则将合计点数加上10点,因为在这种情况下A牌可以视为11点而不至于爆牌(超过21点)。

4. 最后,函数返回累计的点数合计值。

整个函数的作用就是计算玩家手牌的最佳点数总和,在处理A牌时考虑其可能作为1点或11点的灵活性。

目前,键盘用于输入,而不是屏幕上的按钮。

当按下h键时,玩家从牌组中取出一张牌。

输出("拿牌请按h, 停牌请按s, 退出请按q")
输入 = 端口.read("*line") 
如果 输入 == "h" 即
    发牌(闲家,"闲家")
    计算点数(闲家,"闲家")
    输出("拿牌请按h, 停牌请按s, 退出请按q")
    输入 = 端口.read("*line") 
结束

第四、闲家发牌逻辑 (闲家操作) 判断游戏是否结束

这段代码定义了一个名为“二十一点:游戏结束吗”的函数,该函数用于判断游戏是否因闲家点数超过21点而结束。下面是详细的解释:

1. `self.闲家点数 = self:计算点数(self.玩家[1])`: 首先,通过调用“计算点数”函数计算闲家(玩家1)当前手中的牌的总点数,并将该点数赋值给类实例变量`self.闲家点数`。

2. `如果 self.闲家点数 > 21 即`: 检查闲家的点数是否超过了21点。

3. `返回 真`: 如果闲家点数确实超过了21点,那么函数返回`真`(即`true`),表示游戏结束,闲家输了(爆牌)。

4. `结束`: 结束“如果”条件判断。

5. `返回 假`: 如果闲家点数没有超过21点,函数返回`假`(即`false`),表示游戏尚未结束。

总之,这个函数主要是用来检测闲家是否爆牌,如果爆牌则返回`true`,否则返回`false`。在实际游戏流程中,游戏会根据这个函数的返回结果来判断是否应该继续进行下一轮发牌或宣布游戏结果。

-- 闲家发牌逻辑 (闲家操作) 判断游戏是否结束
函数 二十一点:游戏结束吗()
    self.闲家点数 = self:计算点数(self.玩家[1])
    如果 self.闲家点数 > 21 即
        返回 真
    结束
    返回 假
结束

第五、检查玩家是否停牌,并处理相关逻辑

这段代码定义了一个名为“二十一点:要牌吗”的函数,该函数负责处理玩家是否选择要牌(hit)或停牌(stand)的逻辑,同时也处理重新开始游戏(new game)和退出游戏(quit)的选项。以下是函数逻辑的具体说明:

1. 输出提示信息,告知玩家可选操作:

“h-要牌”,“s-停牌”,“b-重新开始”,“q-退出”。

2. 通过`端口.read("*line")`读取玩家从控制台输入的命令,并将其保存在`self.输入值`变量中。
3. 根据玩家输入的指令进行不同操作:

- 如果输入是"h",则调用`self:发牌`函数给当前玩家发一张牌,并显示牌面(`真`表示明牌)。
- 如果输入是"s",则:
- 如果当前玩家是闲家(`self.当前玩家 == 1`),切换到庄家回合(`self.当前玩家 = 2`)。
- 如果输入是"b",则:
- 不进行洗牌操作(注释掉了`self:洗牌()`)。
- 直接调用`self:新游戏()`开始新一轮游戏。
- 输出提示信息“-----------------再来!--------------”。
- 如果输入是"q",则:
- 将闲家点数和庄家点数设置为非有效值(这里是字符串"虚")。
- 使用`系统.exit()`退出当前程序。
- 如果输入既不是"h"也不是"s"、"b"或"q",则认为是无效输入,输出错误提示信息,并返回`假`。

4. 输入有误,跳出程序

在任何情况下,如果没有提前返回,最后默认返回`假`。这是因为在实际游戏中,此函数需要配合其他逻辑判断跳出游戏的循环。

-- 检查玩家是否停牌,并处理相关逻辑
函数 二十一点:要牌吗()
    输出("请选择:h-要牌,s-停牌,b-重新开始,q-退出")
    self.输入值 = 端口.read("*line")
    如果 self.输入值 == "h" 即
        self:发牌(self.玩家[self.当前玩家], 真)
    要么 self.输入值 == "s" 即
        如果 self.当前玩家 == 1 即
            self.当前玩家 = 2 -- 更改为庄家回合
        结束
    要么 self.输入值 == "b" 即
        --self:洗牌()
        --self.当前玩家 = 1
        self:新游戏()
        输出("-----------------再来!--------------")
    要么 self.输入值 == "q" 即
        闲家点数 = "虚"
        庄家点数 = "虚"   
        系统.exit() -- 退出程序
    否则
        输出("无效输入,请重新输入。")
        返回 假
    结束
    返回 假
结束

第六、庄家发牌逻辑 (电脑自动操作)

这段代码定义了一个名为“二十一点:庄家发牌”的函数,用于在游戏中庄家自动发牌的逻辑。以下是该函数的具体解析:

在二十一点游戏中,庄家发牌遵循一定规则,通常是直到点数达到17点或更高时停止拿牌。在此函数中:

1. 使用一个“当...做...结束”循环结构,只要庄家(`self.玩家[2]`)的点数(通过调用`self:计算点数(self.玩家[2])`计算得出)小于17点,循环就会继续执行。

2. 在循环体内,调用`self:发牌(self.玩家[2], 假)`函数给庄家发一张牌,并且不显示庄家收到的这张牌(`假`表示暗牌)。

3. 循环结束后,意味着庄家的点数已经达到或超过17点,庄家不再拿牌。

总结来说,这个函数实现的功能是自动为庄家发牌,直到庄家的点数达到或超过17点为止。在实际游戏中,庄家这一自动发牌逻辑有助于确保游戏公平且符合二十一点的基本规则。

-- 庄家发牌逻辑 (电脑自动操作)
函数 二十一点:庄家发牌()
    当 self:计算点数(self.玩家[2]) < 17 做
        self:发牌(self.玩家[2], 假) -- 不显示庄家手牌
    结束
结束

第七、添加一个用于统一打印玩家手牌的函数

这段代码定义了一个名为“二十一点:开牌”的函数,用于打印展示两位玩家(闲家和庄家)各自的手牌信息。以下是函数的具体解释:

1. 函数开始时,首先输出一行提示信息 "\n闲家手牌:",表示即将展示闲家的手牌。

2. 使用一个“因为...做...结束”循环结构遍历闲家(`self.玩家[1]`)的手牌。这里的“序配”应理解为遍历数组或表中的元素,依次取出闲家手牌列表中的每一张牌。

3. 在循环体内,输出每张牌的花色和点数,格式为“花色, 点数”,如“梅花, 10”。

4. 输出完闲家的手牌后,紧接着输出一行提示信息 "\n庄家手牌:",表示即将展示庄家的手牌。

5. 同样使用一个“因为...做...结束”循环结构遍历庄家(`self.玩家[2]`)的手牌,并逐一输出每张牌的花色和点数。

6. 循环结束后,所有的闲家和庄家手牌信息都被打印出来,展示了当前每位玩家手中持有的所有扑克牌详情。

总之,这个函数的主要功能是在游戏过程中展示两位玩家的手牌,便于玩家了解当前局势。

-- 添加一个用于统一打印玩家手牌的函数
函数 二十一点:开牌()
    输出("\n闲家手牌:")
    因为 _, 手牌 属于 序配(self.玩家[1]) 做
        输出(手牌.花色 .. ', ' .. 手牌.点数)
    结束

    输出("\n庄家手牌:")
    因为 _, 手牌 属于 序配(self.玩家[2]) 做
        输出(手牌.花色 .. ', ' .. 手牌.点数)
    结束

结束

 第八、计算并显示二十一点游戏中的闲家和庄家的点数的函数

函数“二十一点:合计点数”主要负责输出两位玩家(闲家和庄家)的当前点数,并给出游戏菜单选项。以下是详细解读:

1. 函数首先输出闲家的点数,格式为“闲家点数 [具体点数值]”。这里使用了预先计算并存储在`self.闲家点数`中的点数值。

2. 接着,调用`self:计算点数(self.玩家[2])`方法来计算庄家的当前点数,并将结果存入`self.庄家点数`变量中。

3. 输出庄家的点数,格式为“庄家点数 [具体点数值]”。

4. 最后,输出一段游戏菜单,提示用户可以输入'b'重新开始游戏,或输入'q'退出游戏,以供玩家作出下一步操作的选择。

需要注意的是,这个函数并没有处理用户输入的逻辑,只是单纯地展示了当前的点数状况以及游戏菜单选项。在实际游戏中,可能会有另外的函数或逻辑来处理用户根据这些选项作出的决策。

函数 二十一点:合计点数()
    输出("\n闲家点数", self.闲家点数)
    
    self.庄家点数 = self:计算点数(self.玩家[2])

    输出("庄家点数", self.庄家点数)

    输出('--------------------b-重新开始!--------------------q-退出游戏!--------------------')

结束   

第九、游戏主循环

这段代码定义了一个名为“二十一点:开局”的函数,它实现了二十一点游戏的主循环逻辑,具体步骤如下:

1. 给闲家发两张牌,并显示这两张牌(`self:发牌(self.玩家[1], 真)`两次)。
2. 给庄家发两张牌,其中第一张牌不显示(`self:发牌(self.玩家[2], 假)`),第二张牌显示(`self:发牌(self.玩家[2], 真)`)。

3. 进入玩家回合循环,当当前玩家是闲家(`self.当前玩家 == 1`)且游戏尚未结束(`非 self:游戏结束吗()`)时,询问玩家是否要牌(`self:要牌吗()`)。

4. 检查闲家是否爆牌(点数超过21):
   - 如果闲家爆牌,则:
     - 展示所有玩家的手牌(`self:开牌()`)。
     - 显示所有玩家的点数(`self:合计点数()`)。
     - 输出“闲家爆牌,庄家赢”。
   - 否则(闲家未爆牌):
     - 庄家自动发牌直到点数达到17或以上(`self:庄家发牌()`)。
     - 展示所有玩家的手牌(`self:开牌()`)。
     - 显示所有玩家的点数(`self:合计点数()`)。
     - 根据闲家和庄家点数判断游戏胜负:
       - 如果庄家爆牌,则输出“庄家爆牌,闲家赢”。
       - 如果闲家点数大于庄家点数,则输出“闲家赢”。
       - 如果庄家点数大于闲家点数,则输出“庄家赢”。
       - 如果两者点数相等,则输出“平局”。

5. 读取玩家输入(`self.输入值 = 端口.read("*line")`),如果玩家输入的是“b”,则重新开始游戏(`self:新游戏()`)。

这个函数涵盖了游戏的主要流程,包括发牌、玩家决策、庄家自动发牌、胜负判断以及游戏重置等功能。

-- 游戏主循环
函数 二十一点:开局()
    self:发牌(self.玩家[1], 真) -- 给闲家发第一张牌并显示
    self:发牌(self.玩家[1], 真) -- 给闲家发第二张牌并显示
    self:发牌(self.玩家[2], 假) -- 给庄家发第一张牌(不显示)
    self:发牌(self.玩家[2], 真) -- 给庄家发第二张牌并显示

    当 self.当前玩家 == 1 与 非 self:游戏结束吗() 做
        self:要牌吗() -- 玩家选择是否要牌
    结束


    如果 self.闲家点数 > 21 即

        -- 在闲家爆牌或停牌后展示闲家和庄家的所有牌
        self:开牌() -- 展示所有玩家手牌

        self:合计点数() -- 展示所有玩家点数

        输出("闲家爆牌,庄家赢")


    否则
        self:庄家发牌() -- 庄家自动发牌到点数达到17或以上

        self:开牌() -- 展示所有玩家手牌

        self:合计点数() -- 展示所有玩家点数

        -- 判断胜负
        如果 self.庄家点数 > 21 即
            输出("庄家爆牌,闲家赢")
        要么 self.闲家点数 > self.庄家点数 即
            输出("闲家赢")
        要么 self.庄家点数 > self.闲家点数 即
            输出("庄家赢")
        否则
            输出("平局")
        结束
    结束

    self.输入值 = 端口.read("*line") -- 读取玩家输入
    如果 self.输入值 == "b" 即
        self:新游戏() -- 如果输入b,则重新开始游戏
    结束
    
结束

 第十、重新开始游戏及初始化游戏并开始

这段代码中包含两个函数定义及一个游戏初始化的调用:

1. **函数二十一点:新游戏()**:
   - 此函数用于重启新的一局游戏。首先,它调用`self:洗牌()`方法重新洗牌,确保下一局游戏开始时使用全新的、随机排列的牌堆。
   - 然后,清空闲家(`self.玩家[1]`)和庄家(`self.玩家[2]`)的手牌,将它们重置为空列表。
   - 设置当前玩家为闲家,即`self.当前玩家 = 1`。
   - 输出欢迎信息“--------------------再来!--------------------”,提示玩家新的一局即将开始。
   - 最后,调用`self:开局()`方法开始新一局游戏。

2. **初始化游戏并开始**:
   - 创建一个`二十一点`类的新实例,命名为`游戏`,通过调用`二十一点.新()`方法初始化游戏。
   - 初始化完成后,立即调用`游戏:开局()`方法启动游戏的第一局。

综上所述,这段代码实现了游戏的初始化和重启功能,确保每一局游戏开始时所有状态都被正确重置,并能够顺利进入游戏循环。

-- 重新开始游戏
函数 二十一点:新游戏()
    self:洗牌() -- 重新洗牌
    self.玩家[1] = {} -- 重置闲家手牌
    self.玩家[2] = {} -- 重置庄家手牌
    self.当前玩家 = 1
    输出("--------------------再来!--------------------")
    self:开局() -- 开始新一局游戏
结束

-- 初始化游戏并开始
局部 游戏 = 二十一点.新()
游戏:开局()

总结:

这段伪代码描述了一个基于Lua编写的二十一点游戏的核心逻辑实现,其中包括了构建扑克牌集合、洗牌、发牌、计算点数、玩家决策处理、庄家发牌逻辑、显示玩家手牌、总计点数以及游戏主循环等功能。游戏的主要流程如下:

1. **初始化游戏对象**:
   - 创建一个`二十一点`类的对象,该类包含了游戏所需的数据结构和方法。
   - 初始化牌堆,构建一副扑克牌,并使用Fisher-Yates shuffle算法对其洗牌。

2. **发牌过程**:
   - 玩家(闲家)和庄家各发两张牌,闲家的两张牌全部公开,庄家的第一张牌隐藏。
   - 玩家可以通过输入决定是否要牌(hit)或停牌(stand)。
   - 当玩家选择停牌后,庄家根据预设规则自动拿牌至点数达到17或以上。

3. **计算点数**:
   - 实现了一个计算点数的方法,特别处理A牌的点数为1或11以避免爆牌的情况。

4. **游戏流程控制**:
   - 玩家可以选择重新开始游戏(new game)或退出游戏(quit)。
   - 游戏主循环会一直持续到玩家选择结束或满足游戏结束条件(例如闲家爆牌)。

5. **判定胜负**:
   - 游戏结束后,比较闲家和庄家的点数,点数接近但不超过21的玩家获胜;若双方点数相同则是平局;若玩家爆牌,则庄家胜出。

6. **用户交互**:
   - 通过键盘输入控制玩家行为,如'h'表示要牌,'s'表示停牌,'b'表示重新开始游戏,'q'表示退出游戏。

7. **输出信息**:
   - 游戏中适时输出相关信息,如玩家手牌、点数、游戏菜单选项等。

通过这套逻辑框架,可以构建起一个基本的二十一点游戏,但仍需配合适当的用户输入/输出接口(如命令行或图形用户界面)才能成为一个可玩的完整游戏应用程序。