序言

前天晚上开始写之前一直想写的三国杀单挑仿真项目,初始灵感是想要测试四血界孙权单挑四血新王异在不同状态下的优劣(如新王异零装备起手,单+1马起手,+1马和藤甲/仁王盾起手,单雌雄剑起手,单木牛流马起手),后续是想使用强化学习方法进行训练得出界孙权的最优打法,之前听说过四血标孙权可以和四血新王异五五开,以为界孙权的容错会相对高一些,但是看了去年半个橙子上传的老炮杯一局经典的界孙权内奸单挑主公新王异的对局,看下来界孙权实在是太被动了(当然那时候新王异已经神装雌雄+木马了,普遍认为界孙权大劣,但是最后还是界孙权竟然没有靠闪电就完成翻盘,虽然只是险胜。我个人的感觉是王异只要有雌雄剑和+1马基本上就可以一战了,如果摸到木牛流马王异几乎必胜),这个单挑对于界孙权来说要比想象中的困难得多,因为能针对新王异的卡牌就只有三张乐不思蜀,而且木马本质上对于界孙权来说只能存一张牌。

个人觉得能打赢四血新王异的界孙权才配称为是会玩的界孙权,抛开界孙权明显处于过牌或被强控而处于劣势,而只能选择对爆的单挑对局(许攸,曹纯,周舫等),或是明显优势无需探讨的对局,笔者认为比较有趣的应该是与四血曹仁和四血王异之间的单挑,四血界孙权到底应该以何种策略应对,所以很想知道真正会玩的界孙权的最优策略到底是什么样子的。

后来开始写发现需要定义的规则实在是太多了… 即便是写两个白板之间的单挑也需要大量时间,笔者的思路是先定义白板武将作为父类,类中定义武将的体力值,血量值,手牌区,装备区,判定区的卡牌,以及各个摸牌,出牌,弃牌等阶段,以及如何使用卡牌的定义,然后定义孙权和王异分别来继承这个白板类。可以重写相关函数的定义。

目前大致完成了单挑的定义,以及除锦囊牌外其他卡牌的使用定义,尚未完成几个武器的用法定义,以及出牌阶段的规则还没有完善。本来以为能周末偷闲写完,现在看来可能只能暂时烂尾了,以后有时间,还有热情的话再回来写写。其实还是很希望能有人能写出一个可以用于单挑测试的脚本的,毕竟依靠手动测试实在是很费时间,而界孙权的用法实际上又很难通过有限的规则去定义出来,尤其如果将弃牌堆的卡牌也作为特征输入,机器就可以对剩余牌堆中的卡牌作出预判,甚至可以对新王异的手牌作出预判,很多时候对界孙权制衡的选择有很大的影响的。而牌堆加入木牛流马又会极大地改变对局地走向。当然其实强化学习的起点可以直接从界孙权无脑全制衡开始,逐渐去逼近最优的解法,可惜现在连游戏规则都定义不全,主要是感觉花不起这么长时间写个与本业无关的东西了… 又浪费了两天时间做了件蠢事[Facepalm]

代码挂着做个备份,有缘回头再来完善。


目录

  • 序言
  • code(To Be Done)
    • default_deck.txt
    • example_base.py
    • example_character.py
    • example_config.py
    • example_deck.py
    • example_game.py
    • example_utils.py
  • 更新日志

code(To Be Done)

以下各文件置于同一目录即可,主脚本为example_game.py

default_deck.txt

[方片]
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|南蛮入侵|大宛|无懈可击

example_base.py

# -*- coding: UTF-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cnimport randomfrom example_utils import *
from example_config import CardConfigclass BaseCard(object):def __init__(self, **kwargs) -> None:"""卡牌基类;:param id:               必选字段[int], 卡牌编号;:param name:            必选字段[str], 卡牌名称;:param first_class:     必选字段[str], 卡牌一级类别, 取值范围('基本牌', '锦囊牌', '装备牌');:param second_class: 必选字段[str], 卡牌二级类别('普通锦囊牌', '延时锦囊牌', '武器牌', '防具牌', '进攻马', '防御马', '宝物牌');:param area:           可选字段[str], 卡牌所在区域, 取值范围('手牌区', '装备区', '判定区', '弃牌区', '牌堆区', '结算区', '木牛流马区'), 默认值`CardConfig.deck_area`表示卡牌初始位于牌堆;:param suit:            可选字段[str], 卡牌花色, 取值范围('黑桃', '红心', '梅花', '方片'), 默认值None表示无花色;:param point:           可选字段[int], 卡牌点数, 取值范围(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13), 默认值None表示无点数;:param attack_range: 可选字段[int], 武器牌攻击范围, 取值范围(1, 2, 3, 4, 5), 默认值None表示非武器牌;"""self.id = kwargs.get('id')                                       # 设置卡牌编号self.name = kwargs.get('name')                                   # 设置卡牌名称self.first_class = kwargs.get('first_class')                     # 设置卡牌一级分类self.second_class = kwargs.get('second_class')                     # 设置卡牌二级分类self.suit = kwargs.get('suit', None)                           # 设置卡牌花色self.point = kwargs.get('point', None)                           # 设置卡牌点数self.attack_range = kwargs.get('attack_range', None)             # 设置武器的攻击范围self.components = None                                          # 这个是用于将多张牌当作一张牌使用的情形, 如丈八蛇矛拍出两张牌当作杀使用, 类型为list, 默认值None表示非合成卡牌;class BaseCharacter(object):def __init__(self, **kwargs) -> None:"""角色基类;:param id:                  必选字段[int], 卡牌编号;:param health_point_max:    可选字段[int], 体力上限;:param health_point:        可选字段[int], 体力值;:param is_male:              可选字段[bool], 是否为男性角色;"""self.id = kwargs.get('id')                                      # 设置角色编号self.health_point = kwargs.get('health_point', 5)                # 设置体力值self.health_point_max = kwargs.get('health_point_max', 5)         # 设置体力上限self.is_male = kwargs.get('is_male', True)                       # 设置性别self.hand_area = []                                              # 手牌区self.equipment_area = {                                           # 初始化装备区CardConfig.arms: None,                                      # 初始化武器为空CardConfig.armor: None,                                        # 初始化防具为空CardConfig.defend_horse: None,                                 # 初始化防御马为空CardConfig.attack_horse: None,                                # 初始化进攻马为空CardConfig.treasure: None,                                    # 初始化宝物}                                                       self.judgment_area = []                                             # 初始化判定区为空: 这里之所以用list作为判定区而非dict, 是因为判定区的卡牌是有序的self.mnlm_area = None                                          # 初始化木牛流马区是不存在的self.total_liquor = 0                                           # 初始化酒数为0, 每有一口酒, 下一张杀的伤害+1, 酒的效果在回合结束阶段清零self.kill_times = 1                                                 # 初始化可使用的杀的次数为1self.is_masked = False                                          # 初始状态: 未翻面self.is_bound = False                                           # 初始状态: 未横置self.is_alive = True                                            # 初始状态: 存活def round_start_phase(self, deck) -> None:"""回合开始阶段"""passdef preparation_phase(self, deck) -> None:"""准备阶段"""passdef judgment_phase(self, game) -> dict:"""判定阶段"""judgment_result = {CardConfig.lbss: None,                                      # 乐不思蜀判定结果CardConfig.blcd: None,                                        # 兵粮寸断判定结果CardConfig.lightning: None,                                   # 闪电判定结果}for card in self.judgment_area[::-1]:                          # 判定顺序服从后来先判flag = game.wxkj_polling(card, mode='manual')if flag:                                                   # 轮询结果是生效card_name = card.name                                     # 获取判定牌名称: 乐不思蜀, 兵粮寸断, 闪电;judge = fetch_cards(deck=game.deck, number=1)[0]       # 获得牌堆顶第一张牌judgment_result[card_name] = judge                      # 返回判定区的延时类锦囊牌与判定结果牌else:game.deck.discards.append(card)return judgment_resultdef draw_phase(self, deck, number: int=2) -> None:"""摸牌阶段"""cards = fetch_cards(deck=deck, number=number)                    # 从牌堆获得卡牌self.hand_area.extend(cards)                                   # 将卡牌加入到手牌区def play_phase(self, game, mode: str='manual') -> None:"""出牌阶段"""# 定义出杀次数arms = self.equipment_area[CardConfig.arms]if arms is not None and arms.name == CardConfig.zgln:self.kill_times = 999else:self.kill_times = 1while True:card_ids = [card_id for card_id in self.hand_area]if mode == 'manual':ans = input('  - 请输入需要使用的卡牌编号({}): '.format(card_ids))if not ans.isdigit():print('    + 出牌阶段结束!')breakans = int(ans)if not ans in wxkj_ids:print('    + 输入编号不在手牌中!')else:ans = random.sample(card_ids, 1)[0]def fold_phase(self, deck, discards: list=None) -> None:"""弃牌阶段"""overflow = len(self.hand_area) - self.health_point                # 计算手牌溢出数量overflow = max(overflow, 0)                                      # 手牌溢出最小为0# 情况一: 如果没有给定需要弃置的卡牌: 即处于托管状态if discards is None:if overflow > 0:                                          # 如果存在手牌溢出random.shuffle(self.hand_area)                            # 打乱手牌区卡牌auto_discards = []                                        # 随机存储托管弃牌                                     for _ in range(overflow):                                # 弃置溢出的卡牌auto_discards.append(self.hand_area.pop(0))            # 直接从随机从手牌区的起始位置弹出卡牌即可deck.discards.extend(auto_discards)                       # 将弃置的卡牌置入弃牌堆# 情况二: 如果给定了需要弃置的卡牌else:                                                          assert len(discards) == overflow, '需要弃置的卡牌数量不正确'     # 否则要求给定需要弃置的卡牌必须与手牌溢出数量相等card_ids = []# 检验弃置的卡牌都可以在手牌区找到for discard in discards:                                  # 遍历每张需要弃置的卡牌card_ids.append(discard.id)                                # 记录弃置卡牌的编号for card in self.hand_area:                              # 确定其在手牌区中的位置in_hand = False                                       # 判断其是否在手牌区中的标识# if discard.name == card.name and \#     discard.point == card.point and \#    discard.suit == card.suit:                         # 根据卡牌名称, 卡牌点数, 卡牌花色唯一确定if discard.id == card.id:                             # 根据卡牌编号唯一确定in_hand = True                                     # 在手牌区中找到该卡牌breakassert in_hand, '需要弃置的卡牌不在手牌区'                   # 找不到需要弃置的卡牌则抛出异常# 删除需要弃置的卡牌index = -1                                                     # 记录弃置卡牌的位置discards是相同的discards_copy = []                                          # 仅用于验证, 其结果应当与discards相同for card in self.hand_area[:]:                                 # 从手牌区依次删除弃置的卡牌index += 1if card.id in card_ids:                                  # 如果当前卡牌在discards的卡牌编号中discards_copy(self.hand_area.pop(index))             # 删除手牌区的卡牌index -= 1                                           # 删除卡牌后需要将index减一assert len(discards_copy) == len(discards), '意外的错误'        # 确认discards_copy与discards所包含的卡牌数量相等def ending_phase(self, deck) -> None: """结束阶段"""passdef round_end_phase(self, deck) -> None:"""回合结束阶段"""self.liquor_count = 0                                            # 结束阶段将酒数重置arms = self.equipment_area[CardConfig.arms]if arms is not None and arms.name == CardConfig.zgln:self.kill_times = 999else:self.kill_times = 1####################################################################def is_wxkj_exist(self) -> bool:"""判断是否有无懈可击可以使用"""for card in self.hand_area:if card.name == CardConfig.wxkj:return Truereturn Falsedef get_all_wxkjs(self) -> list:"""获取所有无懈可击卡牌"""wxkjs = []for card in self.hand_area:if card.name == CardConfig.wxkj:wxkjs.append(card)return wxkjs       def get_all_peaches(self) -> list:"""获取所有桃"""peaches = []for card in self.hand_area:if card.name == CardConfig.peach:peaches.append(card)return peachesdef get_all_dodges(self) -> list:"""获取所有闪"""dodges = []for card in self.hand_area:if card.name == CardConfig.dodge:dodges.append(card)return dodgesdef ask_for_peach(self, deck, mode: str='manual') -> bool:"""求桃: 单挑模式限定自救"""assert self.health_point <= 0for i in range(1 - self.health_point):                            # 需要求若干次桃peaches = self.get_all_peaches()                          # 获取当前手牌区所有peach_ids = [peach.id for peach in peaches]print('可使用的桃子如下(目前还有{}个桃): '.format(len(peaches)))for peach in peaches:print('  - ', end='')display_card(peach)if len(peaches) == 0:ans = 0else:if mode == 'manual':while True:   ans = input('  - 是否使用桃?(|是->1|否->0|)')if not ans.isdigit():print('    + 请按照要求正确输入!')continueans = int(ans)if not ans in [0, 1]:print('    + 请按照要求正确输入!')breakelse:ans = random.randint(0, 1)if ans == 1:if mode == 'manual':while True:ans = input('  - 请输入需要使用的桃的卡牌编号({}): '.format(peach_ids))if not ans.isdigit():print('    + 请按照要求正确输入!')continueans = int(ans)if not ans in wxkj_ids:print('    + 输入编号不在手牌中!')breakelse:random.sample(peach_ids, 1)[0]# 使用掉手牌区的桃子for i in range(len(self.hand_area)):if self.hand_area[i].id == ans:self.request(deck, self.hand_area.pop(i))else:raise Exception('玩家死亡!')####################################################################def request(self, deck, card, targets: list=None, mode: str='manual', **kwargs) -> dict:"""请求卡牌: 即使用卡牌;:param deck: 当前牌堆;:param card: 使用的卡牌;:param targets: 卡牌使用的对象, 列表元素类型为`BaseCharacter`, 有些卡牌无需指定使用对象, 因为只能对自己使用(如所有装备牌, 酒, 闪电, 无中生有);:param mode: 操作模式, 默认手动操作;:param kwargs: 其他可能的参数;"""card_name = card.name                                           # 获取卡牌名称card_first_class = card.first_class                                # 获取卡牌一级分类card_second_class = card.second_class                            # 获取卡牌二级分类if card_first_class == CardConfig.basic:                        # 使用基本牌if card_name in [CardConfig.kill, CardConfig.fire_kill, CardConfig.thunder_kill]: # [杀]assert len(targets) == 1                                # 杀的目标只能有一个assert not targets[0].id == self.id                        # 杀不能对自己使用distance = calc_distance(source_character=self, target_character=targets[0], base=1)attack_range = calc_attack_range(character=self)if attack_range < distance:raise Exception('距离不够无法使用杀!')armor = targets[0].equipment_area[CardConfig.armor]arms = self.equipment_area[CardConfig.arms]if arms is not None:# 朱雀羽扇if arms == CardConfig.zqys:if mode == 'manual':while True:ans = input('是否使用朱雀羽扇?(|是->1|否->0|)')if not ans.isdigit():print('    + 请按照要求正确输入!')continueans = int(ans)if not ans in [0, 1]:print('    + 请按照要求正确输入!')continuebreakelse:ans = random.randint(0, 1)            if ans == 1:                                   # 使用朱雀羽扇card_name == CardConfig.fire_kill             # 卡牌变为火杀# 雌雄双股剑if arms == CardConfig.cxsgj:if not self.is_male == targets[0].is_male:       # 性别不同if mode == 'manual':while True:ans = input('是否使用雌雄双股剑?(|是->1|否->0|)')if not ans.isdigit():print('    + 请按照要求正确输入!')continueans = int(ans)if not ans in [0, 1]:print('    + 请按照要求正确输入!')continuebreakelse:ans = random.randint(0, 1)            if ans == 1:                                   # 使用雌雄双股剑if len(target.hand_area) == 0:               # 手牌区为空则必须选择摸牌ans = 0if mode == 'manual':while True:ans = input('自己弃牌或是让对方摸牌?(|弃牌->1|摸牌->0|)')if not ans.isdigit():print('    + 请按照要求正确输入!')continueans = int(ans)if not ans in [0, 1]:print('    + 请按照要求正确输入!')continuebreak                                            else:ans = random.randint(0, 1)        if ans == 1:                               # 弃牌card_ids = [card_id for card_id in targets[0].hand_area]if mode == 'manual':while True:ans = input('  - 请输入需要弃置的卡牌编号({}): '.format(card_ids))if not ans.isdigit():print('    + 请按照要求正确输入!')continueans = int(ans)if not ans in wxkj_ids:print('    + 输入编号不在手牌中!')continuebreakelse:ans = random.sample(card_ids, 1)[0]# 雌雄双股剑弃牌for i in range(len(targets[0].hand_area)):if targets[0].hand_area[i].id == ans:deck.discards.append(targets[0].hand_area.pop(i))else:                                        # 摸牌card = fetch_cards(deck=deck, number=1)[0]self.hand_area.append(card)if armor is not None:                                   # 存在防具is_effect = Trueif arms is not None and arms.name == CardConfig.qgj: # 青釭剑无视防具is_effect = True                                # 此杀必然生效elif armor.name == CardConfig.bgz:                    # 八卦阵if mode == 'manual':while True:ans = input('是否使用八卦阵?(|是->1|否->0|)')if not ans.isdigit():print('    + 请按照要求正确输入!')continueans = int(ans)if not ans in [0, 1]:print('    + 请按照要求正确输入!')continuebreakelse:ans = random.randint(0, 1)if ans == 1:card = fetch_cards(deck=deck, number=1)[0]print('  - 判定牌: ')display_card(card)if card.suit in [CardConfig.heart, CardConfig.diamond]:print('  - 八卦阵判定生效: ', end='')is_effect = Falseelif card.suit in [CardConfig.spade, CardConfig.club]:print('  - 八卦阵判定失效: ', end='')else:assert False, '未知的花色!'elif armor.name == CardConfig.rwd:                  # 仁王盾if card.suit in [CardConfig.spade, CardConfig.club]:print('  - 黑杀无效!')is_effect = Falseelif armor.name == CardConfig.ratton_armor:             # 藤甲if not card_name in [CardConfig.fire_kill, CardConfig.thunder_kill]:print('  - 普通杀无效')is_effect = Falseif is_effect:                                             # 若此杀生效if mode == 'manual':while True:ans = input('是否使用闪?(|是->1|否->0|)')if not ans.isdigit():print('    + 请按照要求正确输入!')continueans = int(ans)if not ans in [0, 1]:print('    + 请按照要求正确输入!')continuebreak   else:ans = random.randint(0, 1)                if ans == 1:                                       # 出闪dodges = targets[0].get_all_dodges()dodge_ids = [dodge.id for dodge in dodges]for dodge in dodges:print('  {}: ', end='')display(dodge)if mode == 'manual':while True:ans = input('  - 请选择需要使用的闪卡牌编号({}): '.format(wxkj_ids))if not ans.isdigit():print('    + 请按照要求正确输入!')continueans = int(ans)if not ans in dodge_ids:print('    + 输入编号不在手牌中!')breakelse:ans = random.sample(dodge_ids, 1)[0]for i in range(len(targets[0].hand_area)):if targets[0].hand_area[i].id == ans:deck.discards.append(targets[0].hand_area.pop(i)) if arms is not None:                             # 闪避可能触发的武器效果# 贯石斧if arms.name == CardConfig.gsf:pass# 青龙偃月刀if arms.name == CardConfig.qlyyd:passelse:                                              # 不出闪if arms is not None:                           # 不出闪可能触发的武器效果# 麒麟弓if arms.name == CardConfig.qlg:pass# 寒冰剑if arms.name == CardConfig.hbj:passif armor is not None:if armor.name == CardConfig.bysz:          # 白银狮子减伤害hurt_point = 1else:hurt_point = 1 + self.total_liquorelse:hurt_point = 1 + self.total_liquor# 火杀藤甲加伤害hurt_point += (card_name == CardConfig.fire_kill and armor.name == CardConfig.ratton_armor)# 古锭刀捅菊花加伤害hurt_point += (arms.name is not None and arms.name == CardConfig.gdd and len(targets[0].hand_area) == 0)hurt(deck=deck, character=targets[0], hurt_point=hurt_point)deck.discards.append(card.copy())elif card_name == CardConfig.dodge:                          # [闪]raise Exception('闪不能直接使用!')elif card_name == CardConfig.peach:                          # [桃]if self.health_point == self.health_point_max:raise Exception('未受伤不能使用桃!')self.health_point += 1deck.discards.append(card)elif card_name == CardConfig.liquor:                      # [酒]if self.total_liquor > 0:raise Exception('已经处于酒状态, 无法使用酒!')else:self.total_liquor += 1deck.discards.append(card)else:assert False, '未知的基本牌!'elif card_first_class == CardConfig.tips:                         # 使用锦囊牌# 延时类锦囊if card_second_class == CardConfig.delay_tips:             if card_name == CardConfig.lbss:                       # [乐不思蜀]assert len(targets) == 1                          # 乐不思蜀的目标只能有一个assert not targets[0].id == self.id                     # 乐不思蜀不能对自己使用for card in targets[0].judgment_area:if card.name == CardConfig.lbss:raise Exception('角色判定区已经存在乐不思蜀!')targets[0].judgment_area.append(card.copy())      # 将乐不思蜀置入其判定区if card_name == CardConfig.blcd:                         # [兵粮寸断]assert len(targets) == 1                          # 兵粮寸断的目标只能有一个assert not targets[0].id == self.id                     # 兵粮寸断不能对自己使用for card in targets[0].judgment_area:if card.name == CardConfig.blcd:raise Exception('角色判定区已经存在兵粮寸断!')distance = calc_distance(source_character=self, target_character=targets[0], base=1)if distance > 1:raise Exception('无法对距离为{}的角色使用兵粮寸断!'.format(distance))targets[0].judgment_area.append(card.copy())        # 将兵粮寸断置入其判定区if card_name == CardConfig.lightning:                    # [闪电]for card in self.judgement_area:if card.name == CardConfig.lightning:raise Exception('角色判定区已经存在闪电')# 非延时类锦囊elif card_second_class == CardConfig.immediate_tips:         if card_name == CardConfig.ghcq:                      # [过河拆桥]passif card_name == CardConfig.ssqy:                      # [顺手牵羊]passif card_name == CardConfig.wzsy:                      # [无中生有]passif card_name == CardConfig.tslh:                      # [铁索连环]assert len(targets) < 3, '铁索连环的目标数应当小于3!'if len(targets) == 0:                                # 铁索连环目标数为0指重铸new_card = recast_card(deck=deck, card=card)self.hand_area.append(new_card)else:                                               # 铁索连环目标数为1或2时即为使用for target in targets:target.is_bound = not target.is_bound      # 铁索连环的效果是使处于横置状态的角色重置, 或使处于重置状态的角色横置elif card_name == CardConfig.wxkj:                       # [无懈可击]raise Exception('无懈可击不能直接使用!')elif card_name == CardConfig.nmrq:                         # [南蛮入侵]passelif card_name == CardConfig.wjqf:                        # [万箭齐发]passelif card_name == CardConfig.tyjy:                        # [桃园结义]passelif card_name == CardConfig.wgfd:                        # [五谷丰登]passelif card_name == CardConfig.jdsr:                        # [借刀杀人]passelif card_name == CardConfig.duel:                        # [决斗]passelif card_name == CardConfig.fire_attack:               # [火攻]passdeck.discards.append(card)                                # 所有非延时类锦囊使用后都会置入弃牌堆else:assert False, '未知的锦囊牌二级分类!'  elif card_first_class == CardConfig.equipment:                     # 使用装备牌: 此时target可以为None, 因为必须为自己current_equipment = self.equipment_area[card_second_class]    # 找到当前装备区中的卡牌self.equipment_area[card_second_class] = card                 # 将装备牌置入该区域if current_equipment is not None:                            # 如果需要使用的装备牌对应的装备区已经有卡牌deck.discards.append(current_equipment)                  # 将这张卡牌置入弃牌堆if card_second_class == CardConfig.attack_horse:game == kwargs.get('game')game.else: assert False, '未知的卡牌一级分类'if __name__ == '__main__':pass

example_character.py

# -*- coding: UTF-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cnfrom example_utils import *
from example_deck import Deck
from example_base import BaseCharacterclass SunQuan(BaseCharacter):"""孙权"""def __init__(self, id: int=0, health_point: int=4, health_point_max: int=4, is_male: bool=True) -> None:BaseCharacter.__init__(self,id=id,health_point=health_point,health_point_max=health_point_max,is_male=is_male,)def play_phase(self, game):"""出牌阶段"""BaseCharacter.play_phase(self, game)def zhiheng(self, deck: Deck, discards: list, is_new: bool=True) -> None:"""制衡"""handcard_count = 0                                                 # 统计制衡弃置多少张手牌# 1 检验制衡弃牌在手牌区及装备区可以找到for discard in discards:in_area = False                                                 # 判断其是否在孙权区域中的标识for card in self.equipment_area.values():                   # 遍历孙权装备区的卡牌if discard.id == card.id:                                 # 以卡牌编号作为唯一标识in_area = Truebreakif in_area:continuefor card in self.hand_area:                                     # 遍历孙权手牌区的卡牌                   if discard.id == card.id:                              # 以卡牌编号作为唯一标识in_area = Truehandcard_count += 1                                   # 记录制衡弃置手牌的数量: 便于实现界制衡的额外效果breakassert in_area, '需要制衡的卡牌不在孙权的手牌区或装备区'             # 找不到需要弃置的卡牌则抛出异常# 2 确定制衡获得的卡牌number = len(discards)if is_new and handcard_count == len(self.hand_area):             # 界制衡额外效果实现条件number += 1                                                  # 界制衡额外获得一张牌new_cards = fetch_cards(deck=deck, number=number)                # 从牌堆摸制衡得到的卡牌# 3 将制衡弃牌从孙权的区域中置入弃牌堆deck.discards.extend(discards)class WangYi(BaseCharacter):"""王异"""def __init__(self, id: int=0, health_point: int=3, health_point_max: int=3, is_male: bool=False) -> None:BaseCharacter.__init__(self, id=id,health_point=health_point,health_point_max=health_point_max,is_male=is_male,)def play_phase(self, game):"""出牌阶段"""BaseCharacter.play_phase(self, game)def ending_phase(self, deck: Deck, is_skill: bool=True): """结束阶段"""if is_skill:                                                     # 结束阶段默认发动秘技self.miji(deck)def miji(self, deck):"""秘技"""if self.health_point < self.health_point_max:                  # 秘技发动条件: 已受伤cards = fetch_cards(deck, self.health_point_max - self.health_point)self.hand_area.extend(cards)                              # 秘技摸牌加入到手牌中else:print('不满足秘技发动条件')if __name__ == '__main__':sq = SunQuan()pass

example_config.py

# -*- coding: UTF-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cnclass DeckConfig:    default_deck_filepath = 'default_deck.txt'class CardConfig:  # 1 卡牌名称# 1.1 基本牌kill           = '杀'fire_kill       = '火杀'thunder_kill   = '雷杀'dodge          = '闪'peach           = '桃'liquor          = '酒'# 1.2 锦囊牌# 1.2.1 非延时类锦囊牌ghcq            = '过河拆桥'ssqy         = '顺手牵羊'wzsy         = '无中生有'tslh         = '铁索连环'wxkj         = '无懈可击'nmrq         = '南蛮入侵'wjqf         = '万箭齐发'tyjy         = '桃园结义'wgfd         = '五谷丰登'jdsr         = '借刀杀人'duel         = '决斗'fire_attack        = '火攻'# 1.2.2 延时类锦囊牌lbss         = '乐不思蜀'blcd         = '兵粮寸断'lightning        = '闪电'# 1.3 装备牌# 1.3.1 装备牌: 武器zgln           = '诸葛连弩'qgj              = '青釭剑'cxsgj         = '雌雄双股剑'hbj             = '寒冰剑'gdd               = '古锭刀'zbsm          = '丈八蛇矛'qlyyd            = '青龙偃月刀'gsf             = '贯石斧'zqys          = '朱雀羽扇'fthj         = '方天画戟'qlg              = '麒麟弓'# 1.3.2 装备牌: 防具bgz                = '八卦阵'ratton_armor  = '藤甲'rwd                = '仁王盾'bysz          = '白银狮子'# 1.3.3 装备牌: 进攻马chitu            = '赤兔'dawan          = '大宛'zixing         = '紫骍'# 1.3.4 装备牌: 防御马dilu           = '的卢'zhfd           = '爪黄飞电'jueying          = '绝影'hualiu         = '骅骝'# 1.3.5 装备牌: 宝物mnlm            = '木牛流马'# 2 卡牌类别# 2.1 一级分类basic          = '基本牌'tips          = '锦囊牌'equipment     = '装备牌'# 2.2 二级分类immediate_tips  = '非延时类锦囊'delay_tips     = '延时类锦囊'arms            = '武器'armor          = '防具'defend_horse   = '防御马'attack_horse  = '进攻马'treasure      = '宝物'# 3 卡牌花色spade          = '黑桃'heart          = '红心'club           = '梅花'diamond            = '方片'# 4 卡牌区域hand_area      = '手牌区'equipment_area    = '装备区'judgment_area = '判定区'deck_area     = '牌堆区'settlement_area   = '结算区'discard_area  = '弃牌区'mnlm_area     = '木牛流马区'# 5 其他配置信息name2attr = {kill:           [basic, basic, None],fire_kill:         [basic, basic, None],thunder_kill:  [basic, basic, None],dodge:             [basic, basic, None],peach:             [basic, basic, None],liquor:        [basic, basic, None],ghcq:          [tips, immediate_tips, None],ssqy:          [tips, immediate_tips, None],wzsy:          [tips, immediate_tips, None],tslh:          [tips, immediate_tips, None],wxkj:          [tips, immediate_tips, None],nmrq:          [tips, immediate_tips, None],wjqf:          [tips, immediate_tips, None],tyjy:          [tips, immediate_tips, None],wgfd:          [tips, immediate_tips, None],jdsr:          [tips, immediate_tips, None],duel:          [tips, immediate_tips, None],fire_attack:   [tips, immediate_tips, None],lbss:          [tips, delay_tips, None],blcd:          [tips, delay_tips, None],lightning:         [tips, delay_tips, None],zgln:          [equipment, arms, 1],qgj:           [equipment, arms, 2],cxsgj:             [equipment, arms, 2],hbj:           [equipment, arms, 2],gdd:           [equipment, arms, 2],zbsm:          [equipment, arms, 3],qlyyd:             [equipment, arms, 3],gsf:           [equipment, arms, 3],zqys:          [equipment, arms, 4],fthj:          [equipment, arms, 4],qlg:           [equipment, arms, 5],bgz:           [equipment, armor, None],ratton_armor:  [equipment, armor, None],rwd:           [equipment, armor, None],bysz:          [equipment, armor, None],chitu:             [equipment, attack_horse, None],dawan:          [equipment, attack_horse, None],zixing:         [equipment, attack_horse, None],dilu:           [equipment, defend_horse, None],zhfd:           [equipment, defend_horse, None],jueying:        [equipment, defend_horse, None],hualiu:         [equipment, defend_horse, None],mnlm:           [equipment, treasure, None],}

example_deck.py

# -*- coding: UTF-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cnimport randomfrom example_config import CardConfig, DeckConfig
from example_base import BaseCard
from example_utils import *class Deck(object):def __init__(self, cards: list=None, discards: list=None) -> None:if cards is None:                                               # 没有给定self.load_default_deck()else:                                                             # 否则直接使用给定的卡牌创建新牌堆self.cards = cards# random.shuffle(self.cards)                                       # 洗牌self.discards = [] if discards is None else discards           # 初始化弃牌堆self.id2card = {card.id: card for card in self.cards}          # 构建一个卡牌的索引, 便于查询def load_default_deck(self) -> None:"""默认军争牌堆(带木牛流马, 161张牌)"""self.cards = []cardconfig_dict = get_config_dict(CardConfig)index = -1with open(DeckConfig.default_deck_filepath, 'r', encoding='utf8') as f:lines = f.read().splitlines()# default_deck_filepath配置文件的数据处理for line in filter(None, lines):                                 # 注意删除配置文件中的空行entry = line.strip()if entry.startswith('[') and entry.endswith(']'):            # 花色名称包含在'['与']'之间suit = CardConfig.__getattribute__(CardConfig, cardconfig_dict.get(entry[1:-1]))else:                                                        # 其他每行都是点数与花色相同的数张卡牌名称cells = entry.split('|')                               # 每行的数据使用'|'分隔point = cells[0]                                       # 点数位于行首for cell in cells[1:]:                                  # 其他位置都是卡牌名称name = CardConfig.__getattribute__(CardConfig, cardconfig_dict.get(cell))first_class, second_class, attack_range = CardConfig.name2attr.get(name)index += 1kwargs = {                                          # 新卡牌的参数'id': index,'name': name,'first_class': first_class,'second_class': second_class,'suit': suit,'point': point,'attack_range': attack_range,}card = BaseCard(**kwargs)                             # 创建新卡牌self.cards.append(card)                              # 将新卡牌添加到牌堆中def refresh(self) -> None:"""更新牌堆"""random.shuffle(self.discards)                                  # 弃牌堆洗牌self.cards.extend(self.discards)                                 # 将洗好的弃牌堆卡牌添加到牌堆下面self.discards = []                                               # 置空弃牌堆if __name__ == '__main__':deck = Deck()index = 0for card in deck.cards:index += 1print(index, card.suit, card.point, card.name, card.first_class, card.second_class, card.area, card.attack_range)

example_game.py

# -*- coding: UTF-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cnimport random
from example_utils import *
from example_deck import Deck
from example_base import BaseCharacter
from example_config import CardConfig
from example_character import *class SoloGame(object):def __init__(self, player1: BaseCharacter, player2: BaseCharacter) -> None:self.deck = Deck(cards=None)                                   # 初始化牌堆self.player1 = player1                                          # 先手玩家self.player2 = player2                                           # 后手玩家self.player1.hand_area.extend(fetch_cards(deck=self.deck ,number=4)) # 先手玩家初始手牌self.player2.hand_area.extend(fetch_cards(deck=self.deck ,number=4)) # 后手玩家初始手牌def run(self) -> None:round_count = 0while True:round_count += 1print('===== 第{}轮开始 ====='.format(round_count))print('===== player1 回合开始 =====')player1_draw_flag = Trueplayer1_play_flag = Trueself.player1.round_start_phase(deck=self.deck)print('===== player1 准备阶段 =====')self.player1.preparation_phase(deck=self.deck)print('===== player1 判定阶段 =====')judgment_result = self.player1.judgment_phase(game=self)for key, value in judgment_result.items():print('  - {}: '.format(key), end='')display_card(value)if value is None:continueif key == CardConfig.lbss:if not value.suit == CardConfig.heart:player1_play_flag = Falseelif key == CardConfig.blcd:if not value.suit == CardConfig.club:player1_draw_flag = False            elif key == CardConfig.lightning:if value.suit == CardConfig.spade and value.point <= 9 and value.point >= 2:hurt(deck=self.deck, character=self.player1, hurt_point=3)else:assert False, '未知的延时类锦囊'       print('===== player1 摸牌阶段 =====')if player1_draw_flag:self.player1.draw_phase(deck=self.deck, number=2)else:print('  - 跳过!')print('===== player1 出牌阶段 =====')if player1_play_flag:self.player1.play_phase(game=self)else:print('  - 跳过!')print('===== player1 弃牌阶段 =====')self.player1.fold_phase(deck=self.deck)print('===== player1 结束阶段 =====')self.player1.ending_phase(deck=self.deck)print('===== player1 回合结束 =====')self.player1.round_end_phase(deck=self.deck)self.player1.round_start_phase(deck=self.deck)print('  - player1: ', len(self.deck.cards), len(self.deck.discards))############################################################print('===== player2 回合开始 =====')player2_draw_flag = Trueplayer2_play_flag = Trueself.player2.round_start_phase(deck=self.deck)print('===== player2 准备阶段 =====')self.player2.preparation_phase(deck=self.deck)print('===== player2 判定阶段 =====')judgment_result = self.player2.judgment_phase(game=self)for key, value in judgment_result.items():if value is None:continueif key == CardConfig.lbss:if not value.suit == CardConfig.heart:player2_play_flag = Falseelif key == CardConfig.blcd:if not value.suit == CardConfig.club:player2_draw_flag = False         elif key == CardConfig.lightning:if value.suit == CardConfig.spade and value.point <= 9 and value.point >= 2:hurt(deck=self.deck, character=self.player2, hurt_point=3)else:assert False, '未知的延时类锦囊'   print('===== player2 摸牌阶段 =====')if player1_draw_flag:self.player2.draw_phase(deck=self.deck, number=2)print('===== player2 出牌阶段 =====')if player1_play_flag:self.player2.play_phase(game=self)print('===== player2 弃牌阶段 =====')self.player2.fold_phase(deck=self.deck)print('===== player2 结束阶段 =====')self.player2.ending_phase(deck=self.deck)print('===== player2 回合结束 =====')self.player2.round_end_phase(deck=self.deck)self.player1.round_start_phase(deck=self.deck)print('  - player2: ', len(self.deck.cards), len(self.deck.discards))def display_game(self, max_display=4) -> None:print('#' * 64)print('牌堆剩余{}张'.format(len(self.deck.cards)))for card in self.deck.cards[:max_display]:print(' - ', end='')display_card(card)print('-' * 64)print('弃牌堆中{}张'.format(len(self.deck.discards)))for card in self.deck.discards[:max_display]:print(' - ', end='')display_card(card)print('-' * 64)print('player1: ')display_character(self.player1)print('-' * 64)print('player2: ')display_character(self.player2)print('-' * 64)   def wxkj_polling(self, card, mode: str='manual') -> bool:"""无懈可击轮询:param card: 无懈可击所针对的锦囊牌;:param mode: 操作模式, 目前只写了手动模式(manual);:return is_effect: 锦囊牌是否生效"""       settlement_area = []                                            # 临时结算区current_card = card.copy()                                      # 当前无懈可击针对的卡牌while True:display_card(current_card)print('  - 对上述卡牌生效的无懈可击的轮询开始...')end_flag = True                                               # 结算终止标识for player in [self.player1, self.player2]:                     # 使用轮询模式if player.is_wxkj_exist:                                # 如果当前玩家手牌中有无懈可击if mode == 'manual':while True:ans = input('  - 是否需要使用无懈可击?(|1->是|0->否|)')if not ans.isdigit():print('    + 请按照要求正确输入!')continueans = int(ans)if not ans in [0, 1]:print('    + 请按照要求正确输入!')continuebreakelse:ans = random.randint(0, 1)if ans == 1:                                        # 使用无懈可击wxkjs = player.get_all_wxkjs()wxkj_ids = [wxkj.id for wxkj in wxkjs]for wxkj in wxkjs:display_card(wxkj)if mode == 'manual':while True:ans = input('  - 请选择需要使用的无懈可击卡牌编号({}): '.format(wxkj_ids))if not ans.isdigit():print('    + 请按照要求正确输入!')continueans = int(ans)if not ans in wxkj_ids:print('    + 输入编号不在手牌中!')breakelse:ans = random.sample(wxkj_ids, 1)[0]# 打出手牌中的无懈可击并放入结算区for i in range(len(player.hand_area)):if player.hand_area[i].id == ans:settlement_area.append(player.hand_area.pop(i))breakend_flag = False                               # 有人打出无懈可击则继续结算current_card = settlement_area[-1].copy()       # 结算对象为结算区的最后一张牌else:                                               # 不使用无懈可击continueelse:                                                  # 没有无懈可击可以使用continueif end_flag:                                                # 结算终止breakself.deck.discards.extend(settlement_area)                       # 将结算区所有卡牌置入弃牌堆self.deck.discards.append(card)                                  # 将锦囊牌放入弃牌堆if len(settlement_area) % 2:                                     # 结算区有奇数张无懈可击则锦囊牌失效return Falsereturn True                                                      # 否则锦囊牌生效if __name__ == '__main__':sunquan = SunQuan(id=0, health_point=4, health_point_max=4, is_male=True)wangyi = WangYi(id=1, health_point=4, health_point_max=4, is_male=False)game = SoloGame(player1=sunquan, player2=wangyi)game.run()

example_utils.py

# -*- coding: UTF-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cnfrom example_config import CardConfigdef get_config_dict(config: type) -> dict:"""获取配置类的参数字典(键值反转)"""config_dict = {}for key in dir(config):if key.startswith('__') and key.endswith('__'):continuetry: config_dict[config.__getattribute__(config, key)] = keyexcept:continuereturn config_dictdef display_card(card) -> None:if card is None:print(card)else:print(card.id, card.suit, card.point, card.name)def display_character(character) -> None:print('  - 体力值: {}'.format(character.health_point))print('  - 体力上限: {}'.format(character.health_point_max))print('  - 手牌区: {}张'.format(len(character.hand_area)))for card in character.hand_area:print('    + ', end='')display_card(card)equipment_count = 0for card in character.equipment_area.values():if card is not None:equipment_count += 1print('  - 装备区: {}张'.format(equipment_count))for area, card in character.equipment_area.items():print('    + {}: '.format(area), end='')display_card(card)print('  - 判定区: {}张'.format(len(character.judgment_area)))for card in character.judgment_area:print('    + ', end='')display_card(card)def fetch_cards(deck, number: int=1, from_top=True) -> list:"""从牌堆获取若干张卡牌"""if len(deck.cards) < number:print('  - 触发洗牌')deck.refresh()if len(deck.cards) < number:raise Exception('平局')cards = [deck.cards.pop(0 if from_top else -1) for _ in range(number)]return cardsdef recast_card(deck, card):"""重铸卡牌"""new_card = fetch_cards(deck=deck, number=1)[0]deck.discards.append(card)return new_carddef hurt(deck, character, hurt_point: int=1):"""受到伤害"""armor = character.equipment_area[CardConfig.armor]if armor is not None and armor.name == CardConfig.bysz:              # 白银狮子减伤hurt_point = 1character.health_point -= hurt_pointif character.health_point <= 0:character.ask_for_peach(deck, mode='manual')def calc_distance(source_character, target_character, base: int=1) -> int:"""计算距离"""return max(1, base - (source_character.equipment_area[CardConfig.attack_horse] is not None) + (target_character.equipment_area[CardConfig.defend_horse] is not None))def calc_attack_range(character):"""计算攻击范围"""if character.equipment_area[CardConfig.arms] is None:return 1return character.equipment_area[CardConfig.arms].attack_rangeif __name__ == '__main__':pass

更新日志

  1. 2021-01-10:init commit,未完成的烂活。

【待办】三国杀单挑测试脚本相关推荐

  1. 测试脚本的实用性(续)谈对编写脚本的几点规范

    上篇文章对测试脚本的适用性给出了一些小的技巧,这里对每个项目的测试脚本提出了一点规范意见,按照这个规范去写脚本,就可以满足一些实用性的要求,提高实用性指标. 这里不能理解如何规范编程,这个是完全不同的 ...

  2. 编写 Solidity 测试脚本

    编写 Solidity 测试脚本 与 JavaScript 编写的测试脚本一样,基本特性也一直,支持净室环境,可以访问任意不说过的合约. Truffle的可靠性测试框架是基于以下想法构建的: Soli ...

  3. 用 JavaScript 编写测试脚本

    用 JavaScript 编写测试脚本 Truffle使用 Mocha 测试框架和 Chai 断言,为编写 JavaScript 测试提供了坚实的框架. 让我们深入研究,看看 Truffle 是如何建 ...

  4. DroidPilot 测试脚本详解 (一)

    DroidPilot Designer是Android 应用自动化测试的脚本设计器,也是逐渐趋向于专业化的脚本编缉器,采用的是关键字(KeyWord driven)驱动的的测试方法,通过方便的选取应用 ...

  5. Load Runner测试脚本(tuxedo服务)的编写指南

    1.熟悉loadrunner与c++中调用tuxedo服务的对应API: c++: 对比表 C++中 loadrunner 分配内存 tpalloc() lrt_tpalloc() 释放内存 tpfr ...

  6. android冒烟测试自动化,自动化冒烟测试脚本应当遵循的原则

    自动化冒烟测试脚本应当遵循的原则 发表于:2009-06-29来源:作者:点击数: 自动化冒烟测试脚本应当遵循的原则: 1.覆盖主要功能: 冒烟测试不是 系统测试 或 集成测试 ,所以不需要面面俱到, ...

  7. python怎么写测试脚本语言_手把手带你,用Python写一个Monkey自动化测试脚本!!!...

    一.为什么需要一个测试脚本? 之前讲解了 Android Monkey 命令的使用方式,今天趁着还热乎就手把手用 Monkey 写一个压力测试的脚本.还不了解什么是 Monkey 的,可以看看之前的文 ...

  8. Appium环境的安装与配置,Python测试脚本测试

    Appium自动化测试系列1 - Appium环境的安装与配置 发表于4个月前(2015-01-27 14:34)   阅读(803) | 评论(0) 0人收藏此文章, 我要收藏 赞0 寻找 会'偷懒 ...

  9. loadrunner简单使用——HTTP,WebService,Socket压力测试脚本编写

    先说明一下,本人是开发,对测试不是特别熟悉,但因工作的需要,也做过一些性能测试方面的东西.比较久之前很简单的用过,最近又用到了,熟悉一下.现做一个总结. 使用loadrunner进行压力测试主要分两步 ...

最新文章

  1. seaborn箱图(box plot)可视化、并且在箱图中使用三角形标注均值的位置(showmeans=True)
  2. File类遍历(文件夹)目录功能
  3. 学习笔记(33):Python网络编程并发编程-进程池线程池
  4. 实用常识 | 将桌面文件移动到其他硬盘内
  5. 同步I/O 和 异步I/O
  6. 有关SQL Server代理的常见问题和示例
  7. 《软件测试技术实战:设计、工具及管理》—第2章 2.7节测试用例不应该包含实际的数据...
  8. node-cookie- session
  9. java汉字拼音首字母的获取解决方案
  10. 今日头条阅读量怎么刷_自动刷今日头条阅读量 头条号自己刷阅读量
  11. 资深3D角色建模师对于游戏角色设计浅析:细节与风格
  12. 干货《周志华机器学习详细公式推导版》发布,南瓜书pumpkin-book
  13. 医疗管理系统-预约管理
  14. 基于Citespace软件对WOS文献数据进行可视化的操作
  15. 想知道有哪些缩小视频大小的软件?这几个压缩软件你该知道
  16. 生成计算机组成原理:8位算术逻辑运算实验数据
  17. mysql推binlog流_MySQL中binlog日记清理
  18. 2021年中国私募基金市场日益壮大,私募基金管理规模达到19.78万亿元[图]
  19. 搭载广和通5G LAN模组FM650-CN的5G工业网关已率先商用落地
  20. 洛谷 P1957 【口算练习题】

热门文章

  1. python import lap 出错
  2. 简述防火墙--未知危险的屏障
  3. 微信小程序全局音频设置,手机上播放不了音频的问题解决方案,微信小程序音频无法播放问题.
  4. virtual box和centos问题杂记(一)
  5. 怎么抓雷电模拟器的包_fiddler+雷电模拟器进行APP抓包(可抓HTTPS)
  6. 生成网络论文阅读:PGGAN(一):论文速览
  7. 海思开发板遇到的问题启发性的链接
  8. 如何用纯 CSS 创作一个渐变色动画边框
  9. 少儿编程到底是不是收智商税?
  10. 二极管基本电路之限幅电路