当前位置: 首页 > 后端技术 > Python

DesignOfComputerPrograms(一)-APokerProgram

时间:2023-03-25 22:07:06 Python

来源:UdacityDesignOfComputerProgramsbyPeterNorvig这是一个用python实现的德州扑克程序,讲解的居然是PeterNorvig!然而,那些扑克规则真的让我大吃一惊……在这里做一些笔记来理解逻辑。1.基本设置手:手。一手牌由五张扑克牌组成。rank&suit:手牌点数&花色。比如一张?5,花色(suit)是方块(diamond),等级(rank)是5。牌型手牌等级。牌有几种,按大小降序排列:同花顺:同花顺。同花顺子。4种:手牌中有4张相同点数的牌。fullhouse:三张相同点数的牌+两张相同点数的牌,如10101066.flush:五张相同花色的牌。直:笔直。如56789。3种:手牌中同点数的三张牌。两对:两对相同点数的牌加上一张松牌。对子:一对相同点数的牌加上三张松散的牌。高牌:不符合以上任何一项。大小由具有最高价值的卡片决定。(大小顺序为AKQJ1098765432)。2、程序主体主要完成程序为poker(hands:list)->hand。扑克可以接受手牌列表并返回最大的手牌。hand_rank(手牌)。hand_rank可以指出手牌的牌型。例如,对于?J?J?2?2?5的一手牌,hand_rank将表明这是两对。3、问题(1)Q:python中有没有类似扑克函数的内置函数?A:是的,最大。max中的关键参数允许对被比较的对象进行映射,比如使用abs将原始值变为绝对值。这里可以使用poker返回max(hands,key=hand_rank)来将手牌映射到纸牌类型。这样,扑克逻辑可以简洁地表达为:defpoker(hands):"returnthebesthand:poker([hand,...])=>hand"returnmax(hands,key=hand_rank)(2)如何表示一只手(hand)?如果您将卡片表示为字符串,那么字符串列表就可以了。["TC","9D","10D","JH","5C"]中,“TC”表示10号球杆,“JH”表示JackHeart。(3)Q:如何表示卡类型,使卡类型可以通过max函数进行比较?比如把同花顺分配给8,4-kind分配给7,...,highcard分配给0。这样可以吗?A:不是。4种(101010107和99996)怎么比较?元组可用于表示卡片类型。例如,101010107表示为(7,10,7),999996表示为(7,9,6)。python中的元组也可以相互比较!比较法是依次比较每个元素。比如(7,10,7)大于(7,9,6),因为第一个位置10>9。如何用类似的方式表达其他类型的卡片?同花顺:“同花顺,杰克高!”也就是说,J是同花顺中最大的牌。一旦知道了最高的牌,就可以用同花顺来比较两只手牌的大小。(8,11)完全可以描述手牌情况。4-kind:“四张A,一张Q踢脚!”四个A带一个Q,表示为(7,14,12)。满屋:“满屋,八王!”(6,8,13)表示三个8和两个K。flush:这个需要列出所有牌的点数来比较大小。(5,[10,8,7,5,3])表示同花色的10,8,7,5,3点数为.直:“直,杰克高!”这也只要求点数最多的那手完全可以比较——(4,11)。3种:(3,7,[7,7,7,5,2])表示77752。两对:(2,11,3,[13,11,11,3,3])表示11113313。一对:(1,2,[11,6,3,2,2])表示223116.高牌:(0,7,5,4,3,2)表示75432.这些规则用代码表示:defhand_rank(hand):"返回一个值表示a的排名hand"ranks=card_ranks(hand)#ranks指的是所有手牌点数ifstraight(ranks)andflush(hand):return(8,max(ranks))elifkind(4,ranks):#有4张牌是手上相同的点return(7,kind(4,ranks),kind(1,ranks))elifkind(3,ranks)andkind(2,ranks):return(6,kind(3,ranks),kind(2,ranks))elifflush(hand):return(5,ranks)elifstraight(ranks):return(4,max(ranks))eliftwo_pair(ranks):return(2,two_pair(ranks),ranks)elifkind(2,ranks):return(1,kind(2,ranks),ranks)else:return(0,ranks))####(4)问:如何实现card_ranks并给出卡片的点数在手?A:card_ranks给出手中牌的点数。简单的说就是提取卡片字符串的第一个字符。例如:defcard_ranks(hand):ranks=[rforr,sincards]ranks.sort(reverse=True)returnranks但是像“AC”(俱乐部A)这样的牌呢?最初的想法是这样的:得到一个字典,存储TJQKA对应的值。defcard_ranks(cards):"返回排名列表,从高到低排序。"modifier_dict={'T':10,'J':11,'Q':12,'K':13,'A':14}defmodifier(x):如果x在modifier_dict.keys()中:返回modifier_dict[x]else:returnint(x)ranks=[rforr,sincards]ranks=list(map(modifier,ranks))ranks.sort(reverse=True)returnranks就是这样。然后我还在想,这种“找到key就返回key对应的值,找不到就返回另一个值”是不是很像dict.get(),那就改成:defmodifier(x):modifier_dict={'T':10,'J':11,'Q':12,'K':13,'A':14}returnmodifier_dict.get(x,int(x))嘿,他不会工作。会报错没有int('A')这样的操作。因为不管key能不能找到,get都会执行int,所以你很聪明。然而,彼得只用了一行:defcard_ranks(hand):"返回排名列表,从高到低排序。"ranks=['--23456789TJQKA'.index(r)forr,sinhand]ranks.sort(reverse=True)returnranks使用列表中的位置对应手牌点数,非常漂亮。(4)如何判断顺子(顺子)和同花(同花色)?直的,也就是说点数是5个连续的整数。比较巧妙的方法是判断这5个数互不相同,最大值减最小值为4。max(ranks)-min(ranks)==4andlen(set(ranks))==同花5个,即取和化成套后只有一个元素。len(set(suit))==1这套规则不仅可以用来判断扑克中的同花顺,还可以用来判断五子棋的输赢:五个棋子横坐标相同,五个纵坐标连续取值,则视为获胜。(5)如何实现kind(ranks,n)函数,恰好给出手中的n张牌?最简单的方法就是遍历+list.count()。我的思路也差不多,用collections.Counter再统计一次。但是没必要,没必要把所有的元素都数一遍。defkind(n,ranks):forrinranks:ifranks.count(r)==n:returnrreturnNone(6)two_pair如何实现,判断手上是否有两对牌,return如果有这两套牌的要点?我的思路:先对对方的牌使用kind(ranks,2)得到返回值r0,然后把list中点数为r0的牌全部弹出,对剩下的list使用kind(ranks,2)得到return值值r1。Peter的思路:对方用一次kind(ranks,2)得到r0,反转list,再用kind(ranks,2)得到r1。如果r0不等于r1,则为two_pair。嗯,充分利用data和functions的特点。。。特点是kind只会返回第一手刚好有n张牌的点数,排名按顺序排列。(7)如何处理平局?扑克函数使用max来比较手牌大小,但max只返回第一个最大值,这在平局的情况下是不公平的。所以我们要实现自己的max,它可以返回所有的最大值。这并不难:defallmax(iterable,key=None):result,maxval=[],Nonekey=keyor(lambdax:x)forxiniterable:xval=key(x)ifnotresultorxval>maxval:结果,maxval=[x],xvalelifxval==maxval:结果。append(x)returnresult(8)如何发牌?我们需要给每个玩家随机发n张牌。复杂的写法:defdeal(numhands,n=5,deck=None):deck=copy(deck)or[r+sforrin'23456789TJQKA'forsin'SHDC']hands=[]fornuminrange(0,numhands):hand=[]for_inrange(0,n):card=random.choice(deck)ifdeck:deck.remove(card)hand.append(card)hands.append(hand)returnConcise手写:defdeal(numhands,n=5,deck=None):deck=copy(deck)or[r+sforrin'23456789TJQKA'forsin'SHDC']random.shuffle(deck)return[deck[n*i:n*(i+1)]foriinrange(numhands)]下面的写法不仅算法实现简单,而且更符合日常生活中的情况:谁出牌谁出牌抽牌是随机抽取的。一定是先把整副牌洗好,然后给每人发n张牌。4.测试维基百科给出每种卡片类型出现的概率。其中,最稀有的同花顺概率为0.0015%,最常见的杂牌概率为50.11%。我们可以通过多次运行程序来获取所有卡片类型的出现频率。如果和维基百科的结果一致,就说明我们的程序是正确的。那么,我们应该运行程序多少次呢?虽然次数越多越好,但在实践中必须考虑机器的负担。关键是让最稀有的牌出现足够多的次数以确保结果的稳健性。我们可以期望获得大约10次同花顺,因此程序应该运行10/0.0015%的时间,或大约700,000次。5、重构hand_rank函数rank被多次重复使用,违反了自我重复的禁忌:是否有四张相同rank的牌?哦,不对,同等级的卡有三张吗?哦,不对……那最好一开始就弄清楚自己手里有什么牌,每人有多少张牌。defgroup(items):groups=[(items.count(x),x)forxinset(items)]returnsorted(groups,reverse=True)#把最大的放在前面,如果number相同,把点数最大的放在前面defhand_rank(hand):groups=group(['--23456789TJQKA'.index(r)forr,sinhand])counts,ranks=unzip(组)#710797=>(3,1,1),(7,10,9)如果等级==(14,5,4,3,2):等级=(5,4,3,2,1)#特例规则straight=(len(ranks)==5)and(max(ranks)-min(ranks)==4)flush=(len(set([sforr,sinhand]))==1)return(9if(5,)==else8if顺子else7if(4,1)==countselse6if(3,2)==countselse5ifelse4ifstraightelse3if(3,1,1)==countselse2if(2,2,1)==countselse1if(2,1,1,1)==countselse0),ranks不仅效率更高,也清楚地显示了德州扑克的特点:它把数字5拆分得井井有条。5=4+1=3+2=3+1+1=2+2+1=2+1+1+1=1+1+1+1+1.附加(1)现在有七张牌一手牌,如何选择5张牌并打出最大的牌型?defbest_hand(hand):"""从一手7张牌中,返回最好的5张手牌。"""returnmax(itertools.combinations(hand,5),key=poker.hand_rank)暴力解。取出所有可能的组合并比较最大的一个。(2)手中七张牌中有鬼牌,规定鬼牌可以代表任意一张同色的牌。如何选择5张牌打出最大的牌型?ALL_RANKS='23456789TJQKA'RED_CARDS=[r+sforALL_RANKSforrforsin'DH']BLACK_CARDS=[r+sforrinALL_RANKSforsin'SC']defreplacement(card):ifcard=='?B':返回BLACK_CARDSelifcard=='?R':返回RED_CARDSelse:返回[card]defhas_duplicate(hand):返回Trueiflen(set(hand))!=len(hand)elseFalsedefbest_wild_hand(手):“在所有5张牌选择中尝试王牌的所有值。”possibilities=set(best_hand(p)forpinitertools.product(*map(replacement,hand)))possibilities=[pforpinpossibilitiesifnothas_duplicate(p)]returnmax(possibilities,key=hand_rank)如果你手上有鬼牌,必须先把鬼牌换成普通牌。这里用笛卡尔积来计算置换后手中牌的情况。对于每一种可能的手牌情况,都可以从中找到最好的5张牌。在所有最好的牌中,找到真正最好的。与视频中的标准答案不同的是,这里加入了去重操作,防止假牌被手中已有的牌替换(否则视频中的测试实际上不会通过)。