

Python,速成心法
敲代码,查资料,问度娘
练习,探索,总结,优化

★★★★★博文创作不易,我的博文不需要打赏,也不需要知识付费,可以白嫖学习编程小技巧。使用代码的过程中,如有疑问的地方,欢迎大家指正留言交流。喜欢的老铁可以多多点赞+收藏分享+置顶,小红牛在此表示感谢。★★★★★
---------★Pygame教程★---------
Python经典游戏:太空飞机大战Space Plane Battle
Pygame经典游戏:坦克大战TankWar+五子棋人机对弈+俄罗斯方块(安排!!)
Pygame经典游戏:微信飞机大战Wechatflying(初级版)
Python经典游戏:走迷宫,好烦呀,我到现在还没有走出去!!
Pygame教程03:文本显示+字体加载+transform方法
Pygame教程04:draw方法绘制矩形、多边形、圆、椭圆、弧线、直线和线条等
Pygame教程05:帧动画原理+边界值检测,让小球来回上下运动
Pygame教程06:Event事件的类型+处理方法+监听鼠标事件
Pygame教程08:使用键盘方向键,控制小球,上下左右移动。
Pygame教程09:font.render文本内容,如何自动换行显示
1.记牌器功能:发牌和地主拿底牌时不再扣除记牌器,只有在玩家实际出牌时才扣除。现在记牌器准确显示尚未打出的牌。添加了 ===== 分隔线和标题,使其在输出中更显眼。
==================================================📋 记牌器 (未打出): 3:4 4:4 5:4 6:4 7:4 8:4 9:4 10:4 J:4 Q:4 K:4 A:4 2:4 小王:1 大王:1==================================================
随着出牌,数字会逐渐减少。如果所有牌都打出(游戏结束),记牌器会显示“所有牌均已打出”。

↓ 源码如下 ↓
# -*- coding: utf-8 -*-# @Author : 小红牛# 微信公众号:wdPythonimport randomfrom typing import List, Tuple, Optional, Any# ---------- 牌的定义 ----------RANK_MAP = {'3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, '10': 10,'J': 11, 'Q': 12, 'K': 13, 'A': 14, '2': 15, '小王': 16, '大王': 17}VALUE_TO_RANK = {v: k for k, v in RANK_MAP.items()}SUITS = ['♠', '♥', '♣', '♦']class Card:def __init__(self, rank: str, suit: str = ''):self.rank = rankself.suit = suitself.value = RANK_MAP[rank]def __repr__(self):return f"{self.suit}{self.rank}" if self.suit else self.rankdef __str__(self):return self.__repr__()def __lt__(self, other):return self.value < other.value# ---------- 牌型判断 ----------def get_card_type(cards: List[Card]):if not cards:return 'invalid', Nonesorted_cards = sorted(cards, key=lambda c: c.value)values = [c.value for c in sorted_cards]count_dict = {}for v in values:count_dict[v] = count_dict.get(v, 0) + 1n = len(cards)# 火箭if n == 2 and 16 in count_dict and 17 in count_dict:return 'rocket', 100# 炸弹bombs = [v for v, cnt in count_dict.items() if cnt == 4]if len(bombs) == 1 and n == 4:return 'bomb', bombs[0]# 四带二if len(bombs) == 1:bomb_val = bombs[0]remaining = [v for v in values if v != bomb_val]if len(remaining) == 2:if remaining[0] == remaining[1]:return 'four_with_pair', bomb_valelse:return 'four_with_single', bomb_valelif len(remaining) == 4:rem_count = {}for v in remaining:rem_count[v] = rem_count.get(v, 0) + 1if all(cnt == 2 for cnt in rem_count.values()) and len(rem_count) == 2:return 'four_with_two_pairs', bomb_val# 三张相关trips = [v for v, cnt in count_dict.items() if cnt == 3]if trips:if len(trips) == 1 and n == 3:return 'triple', trips[0]if len(trips) == 1 and n == 4:return 'three_with_one', trips[0]if len(trips) == 1 and n == 5:remaining = [v for v in values if v != trips[0]]if len(remaining) == 2 and remaining[0] == remaining[1]:return 'three_with_pair', trips[0]if len(trips) >= 2:trips_sorted = sorted(trips)if all(v < 15 for v in trips_sorted):if all(trips_sorted[i+1] - trips_sorted[i] == 1 for i in range(len(trips_sorted)-1)):triple_cnt = len(trips)remaining_vals = [v for v in values if v not in trips]rem_n = len(remaining_vals)if rem_n == 0:return 'airplane', (trips_sorted[0], triple_cnt)if rem_n == triple_cnt:rem_count = {}for v in remaining_vals:rem_count[v] = rem_count.get(v, 0) + 1if all(cnt == 1 for cnt in rem_count.values()):return 'airplane_with_single', (trips_sorted[0], triple_cnt)if rem_n == 2 * triple_cnt:rem_count = {}for v in remaining_vals:rem_count[v] = rem_count.get(v, 0) + 1if all(cnt == 2 for cnt in rem_count.values()) and len(rem_count) == triple_cnt:return 'airplane_with_pair', (trips_sorted[0], triple_cnt)# 顺子if n >= 5 and all(cnt == 1 for cnt in count_dict.values()):sorted_vals = sorted(count_dict.keys())if max(sorted_vals) <= 14 and min(sorted_vals) >= 3:if all(sorted_vals[i+1] - sorted_vals[i] == 1 for i in range(len(sorted_vals)-1)):return 'straight', (sorted_vals[0], n)# 连对if n >= 6 and n % 2 == 0 and all(cnt == 2 for cnt in count_dict.values()):sorted_vals = sorted(count_dict.keys())if max(sorted_vals) <= 14 and min(sorted_vals) >= 3:if all(sorted_vals[i+1] - sorted_vals[i] == 1 for i in range(len(sorted_vals)-1)):return 'straight_pair', (sorted_vals[0], n // 2)# 单张if n == 1:return 'single', values[0]# 对子if n == 2 and values[0] == values[1]:return 'pair', values[0]return 'invalid', Nonedef can_beat(last_type: str, last_key: Any, cur_type: str, cur_key: Any) -> bool:if cur_type == 'rocket':return Trueif last_type == 'rocket':return Falseif cur_type == 'bomb' and last_type != 'bomb':return Trueif last_type == 'bomb' and cur_type != 'bomb':return Falseif last_type == 'bomb' and cur_type == 'bomb':return cur_key > last_keyif cur_type != last_type:return Falseif isinstance(last_key, tuple) and isinstance(cur_key, tuple):if last_key[1] != cur_key[1]:return Falsereturn cur_key[0] > last_key[0]else:return cur_key > last_key# ---------- 玩家类 ----------class Player:def __init__(self, name: str):self.name = nameself.hand: List[Card] = []self.is_landlord = Falsedef add_cards(self, cards: List[Card]):self.hand.extend(cards)def remove_cards(self, cards: List[Card]):for card in cards:self.hand.remove(card)def show_hand(self) -> str:sorted_hand = sorted(self.hand)return ' '.join(str(c) for c in sorted_hand)# ---------- 游戏类 ----------class Game:def __init__(self, player_names: List[str]):self.players = [Player(name) for name in player_names]self.deck = self._create_deck()self.bottom_cards: List[Card] = []self.landlord_index: Optional[int] = Noneself.current_player_index: int = 0self.last_play: Optional[Tuple[int, List[Card], str, Any]] = Noneself.pass_count: int = 0# 记牌器:记录每个点数还剩余的张数(初始为全部牌)self.remaining_cards = {rank: 4 for rank in ['3','4','5','6','7','8','9','10','J','Q','K','A','2']}self.remaining_cards['小王'] = 1self.remaining_cards['大王'] = 1def _create_deck(self) -> List[Card]:deck = []for rank, val in RANK_MAP.items():if rank in ('小王', '大王'):deck.append(Card(rank))else:for suit in SUITS:deck.append(Card(rank, suit))random.shuffle(deck)return deckdef deal_cards(self):"""发牌,每人17张,留3张底牌(不扣记牌器,因为牌还在玩家手中)"""for i in range(3):for j in range(17):card = self.deck.pop()self.players[j % 3].add_cards([card])# 发牌时不扣记牌器,牌还在手上未打出self.bottom_cards = self.deckself.deck = []def choose_landlord(self):"""随机选择地主,获得底牌(不扣记牌器)"""self.landlord_index = random.randint(0, 2)self.players[self.landlord_index].is_landlord = Trueself.players[self.landlord_index].add_cards(self.bottom_cards)# 底牌加入地主,仍然未打出,不扣记牌器self.current_player_index = self.landlord_indexprint(f"\n🎲 地主是 {self.players[self.landlord_index].name},底牌: {', '.join(str(c) for c in self.bottom_cards)}")# 游戏开始前显示一次记牌器(此时所有牌都在玩家手中)self.show_card_counter()def show_card_counter(self):"""显示记牌器:按点数顺序列出剩余张数(未打出的牌)"""order = ['3','4','5','6','7','8','9','10','J','Q','K','A','2','小王','大王']parts = []for rank in order:count = self.remaining_cards[rank]# 只显示还剩余>0的牌,如果全部为0则显示“无牌”if count > 0:parts.append(f"{rank}:{count}")print("\n" + "="*50)if parts:print("📋 记牌器 (未打出): " + " ".join(parts))else:print("📋 记牌器: 所有牌均已打出")print("="*50)def next_player(self):self.current_player_index = (self.current_player_index + 1) % 3def play_round(self):"""进行一轮出牌,直到有人获胜"""while True:# 每回合开始显示记牌器self.show_card_counter()player = self.players[self.current_player_index]print(f"\n--- 轮到 {player.name} ---")print(f"手牌: {player.show_hand()}")if self.last_play is None:print("上一手牌: 无")else:last_player = self.players[self.last_play[0]]cards_str = ' '.join(str(c) for c in self.last_play[1])print(f"上一手牌: {last_player.name} 出了 {cards_str}")action = self._player_action(player)if action is None: # 过print(f"{player.name} 不要")self.pass_count += 1if self.pass_count >= 2:self.last_play = Noneself.pass_count = 0else:cards, ctype, key = actionplayer.remove_cards(cards)# 从记牌器中扣除出的牌(这些牌已经打出)for card in cards:self.remaining_cards[card.rank] -= 1print(f"{player.name} 出了: {' '.join(str(c) for c in cards)}")self.last_play = (self.current_player_index, cards, ctype, key)self.pass_count = 0if not player.hand:print(f"\n🏆 {player.name} 获胜!游戏结束。")returnself.next_player()def _player_action(self, player: Player):if player.name == "玩家":return self._human_action(player)else:return self._ai_action(player)def _human_action(self, player: Player):while True:inp = input("请输入要出的牌(点数,空格分隔)或输入 'pass' 过:").strip()if inp.lower() == 'pass':if self.last_play is None:print("现在是你先出牌,不能过")continuereturn Noneranks = inp.split()selected = []hand_copy = player.hand[:]for r in ranks:found = Falsefor card in hand_copy:if card.rank == r:selected.append(card)hand_copy.remove(card)found = Truebreakif not found:print(f"手牌中没有 {r},请重新输入")breakelse:ctype, key = get_card_type(selected)if ctype == 'invalid':print("牌型不合法,请重新出牌")continueif self.last_play is None:return selected, ctype, keyelse:if can_beat(self.last_play[2], self.last_play[3], ctype, key):return selected, ctype, keyelse:print("不能压过上家,请重新出牌")continuedef _ai_action(self, player: Player):hand = player.handif not hand:return Nonedef all_plays(hand):plays = []n = len(hand)for mask in range(1, 1 << n):cards = [hand[i] for i in range(n) if (mask >> i) & 1]ctype, key = get_card_type(cards)if ctype != 'invalid':plays.append((cards, ctype, key))return playsif self.last_play is None:plays = all_plays(hand)for play in plays:if play[1] == 'single':return playreturn plays[0] if plays else Noneelse:last_type, last_key = self.last_play[2], self.last_play[3]plays = all_plays(hand)beats = [p for p in plays if can_beat(last_type, last_key, p[1], p[2])]if beats:return beats[0]else:return None# ---------- 主程序,支持重复游戏 ----------if __name__ == "__main__":print("🎴 欢迎来斗地主!")while True:game = Game(["玩家", "AI1", "AI2"])game.deal_cards()game.choose_landlord()game.play_round()again = input("\n是否再来一局?(y/n): ").strip().lower()if again != 'y':print("感谢游玩,再见!")break
完毕!!感谢您的收看
--------★★历史博文集合★★--------
