diff --git a/examples/run_random.py b/examples/run_random.py index 9a268128..06f74b2b 100644 --- a/examples/run_random.py +++ b/examples/run_random.py @@ -38,7 +38,7 @@ def run(args): parser.add_argument( '--env', type=str, - default='leduc-holdem', + default='bridge', choices=[ 'blackjack', 'leduc-holdem', diff --git a/rlcard/envs/tractor.py b/rlcard/envs/tractor.py new file mode 100644 index 00000000..58c6b4ae --- /dev/null +++ b/rlcard/envs/tractor.py @@ -0,0 +1,8 @@ +import numpy as np +from collections import OrderedDict + +from rlcard.envs import Env +from rlcard.games.uno import Game +from rlcard.games.uno.utils import encode_hand, encode_target +from rlcard.games.uno.utils import ACTION_SPACE, ACTION_LIST +from rlcard.games.uno.utils import cards2list diff --git a/rlcard/games/tractor/__init__.py b/rlcard/games/tractor/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/rlcard/games/tractor/baselinePolicy.py b/rlcard/games/tractor/baselinePolicy.py new file mode 100644 index 00000000..3834901e --- /dev/null +++ b/rlcard/games/tractor/baselinePolicy.py @@ -0,0 +1,86 @@ +import random +import numpy as np +from functools import cmp_to_key + +import tractor_game +# from DNQ.mygame.TractorArtifice.cheater import mkDeck, cheator1 +from tractor_gameUtil import fenInd,getNum +from tractor_game import Tractor +from tractor_player import Player +env=Tractor() +def _baselinecmp(a,b):#比较两组牌大小。返回1是a大,返回0是b大 + return a[1]>b[1] and 1 or -1 +def baselineColdeck(env:Tractor,p: Player,undercard_i):#贪心的换牌算法,cards有8张 + li = [] + for i in range(4): + if i!=p.lordDecor and len(p.cards[i])>0: + li.append((i,len(p.cards[i]))) + li = sorted(li, key=cmp_to_key(_baselinecmp)) + mincard=0 + minnum=100 + for _ in li:#无脑扣每个类别里最小的牌 + kind,cardslen=_[0],_[1] + for card in reversed(p.cards[kind]): + if minnum>env.orderInd[card] and fenInd[getNum(card)]==0: + mincard,minnum=card,env.orderInd[card] + break + if mincard==0:#只有主牌,则从主牌里扣 + return p.cards[p.lordDecor][-1] + return mincard + +def bidFun_random(env,p,round,allActList):#回调函数 + n=len(allActList) + return random.randint(0,n-1) +def firstPolicyFun_random(env,p,usedAct,allActList):#回调函数 + n = len(allActList) + act_id = random.randint(0, n - 1) + # dfsPrintActList(allActList ) + # dfsPrintActList(allActList[act_id]) + # env.printAllInfo(p.id) + # print("") + return act_id +def otherPolicyFun_random(env, p, usedAct, allActList):#回调函数 + n = len(allActList) + act_id=random.randint(0, n - 1) + # dfsPrintActList(allActList ) + # dfsPrintActList(allActList[act_id]) + # env.printAllInfo(p.id) + # print("") + return act_id + +# def baselineAct(p,knowCards,cardsCnt,knowCards_seq,maxi,kind):#player_cards代表自己玩家的手牌,knowCards是已知前置位的牌,knowCards_seq代表第几个出 +# #cardsCnt代表knowCards每组牌的数量 +# #maxi是最大的玩家的位置,kind代表出的花色,级牌算主牌的花色 +# #player_cards通过排序被编码成了4个颜色,每个颜色又分为3组分别代表;oneList,doubleList,traList +# # +# cardsMax=knowCards[maxi] +# player_cards=p.toSortCardsList1(env) +# if knowCards_seq==0:#先出 +# #算跑了多少分,如果外置位分数大于50,则无脑出大的。外置位分数计算方式:200-自己的分数-底牌的分数(如果可见)-已经出去的别人的分数 +# #如果外置位分数不大于50,随机尽量出小的,可以出分 +# # +# pass +# elif knowCards_seq==1:#第二个出 +# #无脑大过0号,否则随机跟小牌且尽量不跟分 +# if len(cardsMax[2])>0:#敌方又拖拉机 +# if len(player_cards[kind][2])==0:#我方没拖拉机 +# if len(player_cards[kind][1])==0:#我方没对子 +# if len(player_cards[kind][0])>=cardsCnt:#这一类花色有牌可出 +# cards=baselineAct_followSmall1()#跟小牌 +# return +# +# # else:#个数大于它且比他多 +# for a in cardsMax[2]: # 看看有没有拖拉机 +# pass +# pass +# elif knowCards_seq == 2:#第三个 +# #如果1号大,就无脑大过1号 +# #如果0号大,且0号是王或级牌或大于1张的甩牌拖拉机对子,就跟分,没有分跟小牌 +# #如果0号大,且0号较小,无脑大过他 +# +# pass +# elif knowCards_seq == 3:#最后出牌,策略是: +# #如果我方大:就无脑跟分,能用分杀就用分杀,没有分就随机跟小牌; +# #如果敌方大:且没有分:能用分杀就用分杀,否则就随机跟小牌且尽量不跟分; +# #如果敌方大且有分:就尽量大过前面的,大不过就随机跟小牌且尽量不跟分 +# pass diff --git a/rlcard/games/tractor/example.py b/rlcard/games/tractor/example.py new file mode 100644 index 00000000..7b7b673a --- /dev/null +++ b/rlcard/games/tractor/example.py @@ -0,0 +1,81 @@ + +#使用该游戏环境的样例 +import random + +from DNQ.mygame.TractorArtifice.game_env.tractor_cheat import mkDeck, cheator1 +from baselinePolicy import baselineColdeck +from tractor_action import dfsPrintActList +from tractor_game import Tractor +def bidFun(env,p,round,allActList):#回调函数 + n=len(allActList) + return random.randint(0,n-1) +def firstPolicyFun(env,p,usedAct,allActList):#回调函数 + n = len(allActList) + act_id = random.randint(0, n - 1) + # dfsPrintActList(allActList ) + # dfsPrintActList(allActList[act_id]) + # env.printAllInfo(p.id) + # print("") + return act_id +def otherPolicyFun(env, p, usedAct, allActList):#回调函数 + n = len(allActList) + act_id=random.randint(0, n - 1) + # dfsPrintActList(allActList ) + # dfsPrintActList(allActList[act_id]) + # env.printAllInfo(p.id) + # print("") + return act_id +def playAGame(env):#4个人双方随机游戏 + # deck1=[39, 39, 23, 12, 26, 5, 53, 1, 38, 30, 46, 54, 48, 40, 36, 6, 28, 46, 26, 18, 7, 16, 2, 27, 5, 22, 20, 47, 41, 41, 34, 8, 3, 31, 30, 13, 16, 23, 15, 48, 13, 51, 4, 37, 44, 33, 25, 52, 34, 9, 37, 21, 3, 17, 50, 29, 24, 51, 49, 38, 35, 43, 24, 6, 18, 32, 22, 29, 7, 20, 11, 19, 15, 36, 14, 42, 27, 45, 14, 12, 50, 45, 52, 31, 11, 42, 40, 47, 33, 54, 32, 8, 28, 21, 10, 49, 9, 25, 53, 44, 1, 4, 17, 19, 10, 2, 35, 43] + # deck1, setDecor, setNum, setDealer=mkDeck(cheator1) + # env.dealCards(deck1,bidFun,(setDecor, setNum , setDealer)) + env.dealCards(None, bidFun) + # env.printAllInfo() + env.setUnderCards(baselineColdeck)#换底牌,baselineColdeck是基于贪心的换底牌策略:无脑扣小排 + env.printAllInfo() + dfsPrintActList(env.players[env.dealer].cards[env.lordDecor]) + firstPlayerId=env.dealer + isTer=False + epoch=0 + while(not isTer):#开始出牌 + # env.printAllCards() + # print("轮次:",epoch," 先出牌玩家:",firstPlayerId) + act4 = [None,None,None,None] + # print("先出玩家:", firstPlayerId) + act4[firstPlayerId] = env.firstPolicy(firstPlayerId,firstPolicyFun)#获取动作 + # firstKind=env.getActKind(act4[firstPlayerId]) + + # print(env.players[firstPlayerId].cards_decorList) + # env.dfsPrintActList(sortCardList2[firstPlayerId]) + # env.dfsPrintActList(allAct[firstPlayerId],printCmp) + # env.dfsPrintActList(act[firstPlayerId] ) + # print(firstKind) + # act[firstPlayerId].println() + for i in range(1,4): + nextID=(firstPlayerId+i)%4 + act4[nextID]= env.otherPolicy(act4,firstPlayerId,nextID,otherPolicyFun) + + firstPlayerId,sc,isTer,endSc=env.step(act4,firstPlayerId)#评价谁赢,返回赢者id,本轮分数(双方都会得分),isTer是游戏有木有结束 + # reset + # env.printAllInfo(firstPlayerId,act4) + if isTer : + # env.printUnderCards() + sc=env.sumSc + winPlayer,playerId,grade=env.calcGameScore()#重置游戏,playerId==-1代表继续,否则代表先达到A的玩家。 + print(sc,winPlayer) + isTer=playerId!=-1 + return isTer,winPlayer,grade + epoch+=1 + return -1,-1 + +def train_game(trainMaxCnt): + env=Tractor() + for _ in range(trainMaxCnt): + env.reset_game() + while (True):#有先超过A的玩家就游戏结束 + isTer,winPlayer,grade=playAGame(env) + # print(env.levelOfBoth) + if isTer: + break + +train_game(1) \ No newline at end of file diff --git a/rlcard/games/tractor/game.py b/rlcard/games/tractor/game.py new file mode 100644 index 00000000..b1d7fd06 --- /dev/null +++ b/rlcard/games/tractor/game.py @@ -0,0 +1,67 @@ +import random + +from tractor_game import Tractor + +class TractorGame(Tractor): + def __init__(self): + super().__init__() # 调用父类的初始化方法 + self.allow_step_back = False + self.isBeginGame=False#是否是游戏开始阶段 + # 以下是rlcard的通用方法: + def init_game(self): + playerId=0 + if self.dealer!=-1:#不是-1说明是非初始阶段 + winPlayer,playerId,grade=self.calcGameScore() + if playerId!=-1: + self.reset_game() + return [], self.currentPlayer + else: + return [], (self.dealer+1)%4 + + def get_num_players(self): + return 4 + + def get_num_actions(self): + return 25 + + def configure(self, game_config): + """ + Specify some game specific parameters, such as number of players, initial chips, and dealer id. + If dealer_id is None, he will be randomly chosen + """ + self.num_players = game_config['game_num_players'] + # must have num_players length + self.init_chips = [game_config['chips_for_each']] * game_config["game_num_players"] + self.dealer_id = game_config['dealer_id'] + def step(self,act):#step + next_state=0 + player_id=0 + if self.game_stage=="bid": + player_id=0 + env.dealCards(None, bidFun) + ''' + 把回调拆开 + ''' + + elif self.game_stage=="ambush": + + elif self.game_stage == "play": + + + return next_state, player_id + + def step_back(self): + pass + def is_over(self): + return self.isTer + def get_player_id(self): + #返回当前出牌的玩家id + return self.currentPlayer + +def bidFun(env,p,round,allActList):#回调函数 + n=len(allActList) + return random.randint(0,n-1) +env=TractorGame() +env.reset_game() +env.dealCards(None, bidFun) +print(env.get_player_id()) diff --git a/rlcard/games/tractor/tractor_action.py b/rlcard/games/tractor/tractor_action.py new file mode 100644 index 00000000..a79b8712 --- /dev/null +++ b/rlcard/games/tractor/tractor_action.py @@ -0,0 +1,143 @@ +import math +import random +from functools import cmp_to_key +import numpy as np + +from tractor_gameUtil import getNum, fenInd, printCard, cardToString, INF, getKind + + +class Action(): + def __init__(self, one=[],double=[],playerId=-1): # double里包含对子和拖拉机,如[[3,3],[4,4,5,5]] + self.one=one.copy() + self.double = double.copy() + self.len=len(one) + self.playerId=playerId + for dou in double: + self.len+=len(dou) + def add(self,one=[],double=[]): + for a in one: + self.one.append(a) + self.len += len(one) + for dou in double.copy(): + self.double.append(dou) + self.len += len(dou) + def addOne(self,a): + self.one.append(a) + self.len += 1 + def addDou(self,dou): + self.double.append(dou.copy()) + self.len+=len(dou) + def setDou(self,i,dou): + if i1 + def getDouleCnt(self):#返回对子数量 + return (self.len-len(self.one))//2 + def getDouleLen(self):#返回对子数组长度 + return len(self.double) + def isSeq(self):#是否为甩牌 + return len(self.double)+len(self.one)>1 + def getFen(self): + sc=0 + for dou in self.double: + for a in dou: + num = getNum(a) # 点数,[1,13]王是14 + sc += fenInd[num] # 分数 + for a in self.one: + num = getNum(a) # 点数,[1,13]王是14 + sc += fenInd[num] # 分数 + return sc + def print(self,i=0): + print("act"+str(i)+":",end="") + i=0 + for dou in self.double: + for a in dou: + printCard(a,i) + i+=1 + for a in self.one: + printCard(a,i) + i+=1 + def println(self,i=0): + self.print(i) + print("") + def toString(self): + ans="" + for dou in self.double: + for a in dou: + ans+=cardToString(a) + for a in self.one: + ans+=cardToString(a) + return ans + + def tolist(self): + li=self.one.copy() + for dou in self.double: + for a in dou: + li.append(a) + return li + def getKind(self,env): + if len(self.one)>0: + return getKind(self.one[0],env.lordDecor,env.lordNum) + return getKind(self.double[0][0], env.lordDecor, env.lordNum) + def sort(self,env): + self.one.sort(key=cmp_to_key(env._sortCardList_cmp1)) + for dou in self.double: + dou.sort(key=cmp_to_key(env._sortCardList_cmp1)) + def getMinCard(self,env):#返回最小的牌 + mincard=0 + minOrder=INF + for dou in self.double: + for a in dou: + if minOrder>env.orderInd[a]: + minOrder =env.orderInd[a] + mincard=a + for a in self.one: + if minOrder > env.orderInd[a]: + minOrder = env.orderInd[a] + mincard = a + return Action([a]) + +def __dfsPrintActList(newLi, li0,printFun=None):#printFun是打印这张牌的条件 + if isinstance(li0,np.ndarray): + n=li0.shape[0] + else: + n=len(li0) + + for i in range(n): + if isinstance(li0[i],int) or isinstance(li0[i],np.int32) or isinstance(li0[i],np.int64): + if printFun==None or printFun(li0[i]): + newLi.append(cardToString(li0[i])) + elif isinstance(li0[i],Action): + if printFun==None or printFun(li0[i]): + newLi.append(li0[i].toString()) + else: + t=[] + __dfsPrintActList(t,li0[i]) + if printFun==None or printFun(t): + newLi.append(t) +def dfsPrintActList(li,printFun=None): + newLi=[] + if isinstance(li,int) or isinstance(li,np.int32): + newLi.append(cardToString(li)) + elif isinstance(li,Action): + newLi.append(li.toString()) + else: + __dfsPrintActList(newLi,li,printFun) + print(newLi) + +def cardsListToAction(env,pid,cards):#cards只有一个类型 + li=(env.sortCardList2(cards)) + li=(env.sortCardList1(li[0], li[1], li[2])) + dou=[] + for a in li[1]: + dou.append([a,a]) + for tra in li[2]: + dou.append([tra[i//2] for i in range(len(tra)*2)]) + act = Action(li[0], dou, playerId=pid) + act.sort(env) + return act diff --git a/rlcard/games/tractor/tractor_cheat.py b/rlcard/games/tractor/tractor_cheat.py new file mode 100644 index 00000000..d715a1f1 --- /dev/null +++ b/rlcard/games/tractor/tractor_cheat.py @@ -0,0 +1,105 @@ +import numpy as np + +from DNQ.mygame.TractorArtifice.game_env.tractor_gameUtil import getKind, INF, printCard, CARDS_CNT, CARDS_CNT2, \ + NameTodecorId, decorName, stringToCardId, UNDERCARD_CNT + + +def __mkdeck_push(deck1,i,yu,pid,without,setDecor, setNum): + if deck1[i] == 0: + j = 0 + while (j < len(yu)): + if without[pid][getKind(yu[j], setDecor, setNum)] == 0: + break + j += 1 + if j == len(yu): + j -= 1 + print("有冲突,放置了b:", end=" ") + printCard(yu[j]) + deck1[i] = yu[j] + yu.pop(j) + elif deck1[i] == INF: + deck1[i] = yu[0] + yu.pop(0) +numind={"A":1,"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"8":1,"9":1,"10":1,"11":1,"12":1,"13":1,"J":1,"Q":1,"K":1} +def mkDeck(fun):#作弊洗牌,测试用 + deck1 = np.zeros((CARDS_CNT), dtype='int') + ind=np.zeros((CARDS_CNT2+1), dtype='int') + yu=[] + without=[[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]#without[i][j]==1代表玩家i不能有颜色j的牌 + cardDir,setDecor,setNum,setDealer=fun() + setDecor=NameTodecorId[setDecor] + for i in range(4):#设置每个玩家的手牌 + dir=cardDir[i] + deck1_i=0 + for j in range(4): + strDec=decorName[j] + if strDec in dir: + for k in range(len(dir[strDec])): + if dir[strDec][k]!="大王" and dir[strDec][k]!="小王" and (dir[strDec][k] in numind ): + dir[strDec][k]=strDec+dir[strDec][k] + a=stringToCardId[dir[strDec][k]] + deck1[deck1_i*4+i] = a + if a!=INF: + ind[a]+=1 + if ind[a]>2: + print("错误") + return None + deck1_i+=1 + without[i][j]=1 + for i in range(len(cardDir[4])):#cardDir[4]代表牌堆的底牌 + a = stringToCardId[cardDir[4][i]] + deck1[CARDS_CNT-i-1]=a + if a != INF: + ind[a] += 1 + if ind[a] > 2: + print("错误") + return None + + for i in range(1,CARDS_CNT2+1): + if ind[i]==1: + yu.append(i) + elif ind[i]==0: + yu.append(i) + yu.append(i) + print(without) + for i in range(CARDS_CNT-UNDERCARD_CNT,CARDS_CNT): + __mkdeck_push(deck1, i, yu, setDealer, without, setDecor, setNum) + # print(deck1) + for k in range(4): + for i in range(k,CARDS_CNT-UNDERCARD_CNT,4): + __mkdeck_push(deck1,i,yu,k,without,setDecor, setNum) + # print(deck1) + # print(deck1) + # ind = np.zeros((CARDS_CNT2 + 1), dtype='int') + # for a in deck1: + # ind[a]+=1 + # print(ind) + return deck1,setDecor,setNum,setDealer + +def cheator1():##作弊器 + setDecor="♠" + setNum=2 + setDealer=0 + dir=[{},{},{},{},[]] + dir[0]["♠"]=["A","A"] + dir[0]["♣"] = ["7","7","6","6","5","5"] + # dir[0]["♦"] = [] + dir[0]["♥"] = ["7","7","6","6","5","5"] + + # dir[1]["♠"] = ["A", "A"] + dir[1]["♣"] = ["8", "8", "10", "A", "A"] + # dir[1]["♦"] = ["8", "8", "10", "A", "A"] + dir[1]["♥"] = ["K", "K", "Q", "Q", "8", "8"] + + dir[2]["♠"] = ["Q", "Q","3", "3"] + # dir[2]["♣"] = ["7", "7", "6", "6", "5", "5"] + # dir[2]["♦"] = [] + dir[2]["♥"] = ["A", "A"] + + # dir[3]["♠"] = ["9", "9", "J", "J"] + # dir[3]["♣"] = [] + # dir[3]["♦"] = [] + # dir[3]["♥"] = [] + + + return dir,setDecor,setNum,setDealer \ No newline at end of file diff --git a/rlcard/games/tractor/tractor_game.py b/rlcard/games/tractor/tractor_game.py new file mode 100644 index 00000000..62dada1c --- /dev/null +++ b/rlcard/games/tractor/tractor_game.py @@ -0,0 +1,888 @@ +import math +import random +from functools import cmp_to_key + +import numpy as np + +from tractor_action import Action, dfsPrintActList, cardsListToAction +from tractor_gameUtil import CARDS_CNT2, HANDCARD_CNT, UNDERCARD_CNT, CARDS_CNT, getKindAndNum, INF, getKind, fenInd, \ + getNum, decorName, getDecor, toCardIndex, printCard, printCardln +from tractor_player import Player + + +class Tractor(): + def __init__(self): + self.undeck = [] # 存数组 + self.reset_game() + def __reset(self): + self.players = [Player(0), Player(1), Player(2), Player(3)] # 0代表没有 + self.lordDecor = -1 # 主牌花色 + self.sumSc = 0 # 闲家得分 + self.underCards = [] + self.game_stage="bid"#bid,ambush,play,分别代表发牌,扣牌,出牌 + + ''' + 如果game_stage=="bid",则currentPlayer代表当前收到牌的玩家 + 如果game_stage=="ambush",则currentPlayer代表当前扣牌的玩家 + 如果game_stage=="play",则currentPlayer代表当前出牌的玩家 + ''' + self.currentPlayer = 0 + + self.isTer=False#游戏是否结束,它在step里会改变 + self.round_i = 0 # 轮数 + self.deck = [i%CARDS_CNT2+1 for i in range(CARDS_CNT)] + self.useCards_i=0#放回deck + + def reset_game(self):#重置游戏进度 + self.__reset() + self.roundGame_i=0 + self.levelOfBoth = [2, 2] + self.dealer = -1 + self.lordNum = 2 # 级牌 + for i in range(4): + self.players[i].setNum(2) + def calcGameScore(self):#结算一局游戏的分数,更新级牌,但不重置游戏进度,preSc代表上局闲家的分数,返回-1代表继续游戏,返回庄家编号代表谁先达到A + preSc=self.sumSc + self.__reset() + grade = self.getGrade(preSc)#结算等级 + if grade>=0:#换庄 + g=grade + self.dealer =(self.dealer+1)%4#换庄 + a = self.levelOfBoth[self.dealer%2] + if 2<=a <= 5: # 5,10,k不能跳 + self.levelOfBoth[self.dealer%2] = min(a + g, 5) + elif 50: + return self.getWinPlayer(),self.dealer,grade + else: + g = -grade + self.dealer = (self.dealer + 2) % 4 # 换同伙坐庄 + a = self.levelOfBoth[self.dealer%2] + if 2 <= a < 5: # 5,10,k不能跳 + self.levelOfBoth[self.dealer % 2] = min(a + g, 5) + elif 5 <= a < 10: + self.levelOfBoth[self.dealer % 2] = min(a + g, 10) + elif 10 <= a < 13: + self.levelOfBoth[self.dealer % 2] = min(a + g, 13) + else: + self.levelOfBoth[self.dealer % 2] = 1 + if a==1 or a==13 and g>1: + return self.getWinPlayer(),self.dealer,grade + self.lordNum=self.levelOfBoth[self.dealer%2] + self.roundGame_i+=1 + for i in range(4): + self.players[i].setNum(self.lordNum) + return self.getWinPlayer(),-1,grade + def getWinPlayer(self): + grade = self.getGrade(self.sumSc) # 结算等级 + winplayer=self.dealer + if grade>=0: + winplayer=(winplayer+1)%4 + return winplayer + def setDealerID(self,dealer): + self.players[dealer].dealerTag=0 + self.players[(dealer+1)%4].dealerTag = 2 + self.players[(dealer+2)%4].dealerTag = 1 + self.players[(dealer + 3) % 4].dealerTag =3 + + def getNowUsedCards(self): + cardList=self.deck[0:self.useCards_i] + return cardList + + def _updateSortCardList(self,sortCardList:list,act:list): + if sortCardList == None: + return + for a in act: # 刷新sortCardList + kind, num = getKindAndNum(a, self.lordDecor, self.lordNum) + li = sortCardList[kind] # 遍历这个花色 + for i in range(len(li[0])): # 从单牌里删除 + if li[0][i] == a: + li[0].pop(i) + break + for i in range(len(li[1])): # 从对子里删除 + if li[1][i] == a: + li[1].pop(i) + break + # print(sortCardList) + for i in range(len(li[2])): + # print(li[2]) + m = len(li[2][i]) + for j in range(m): # 遍历每个拖拉机 + if li[2][i][j] == a: + tra = li[2].pop(i) + beginTra = tra[:j] + endTra = tra[j + 1:] + if len(beginTra) > 1: # 剩下的长度大于1,放回li[2] + li[2].append(beginTra) + if len(endTra) > 1: # 剩下的长度大于1,放回li[2] + li[2].append(endTra) + break + else:#这段用来代替goto,表示找到if时,跳出2层循环。python用goto要单独安装包 + continue + break + # print(sortCardList) + li[2].sort(key=cmp_to_key(self._sortCardList_cmp2)) + + def checkReDealCards(self): + if self.lordDecor<4 and self.players[0].getLordCnt()+ self.players[2].getLordCnt()<10: + return True + if self.lordDecor<4 and self.players[1].getLordCnt()+ self.players[3].getLordCnt()<10: + return True + # for i in range(4): + # if self.players[i].getLordCnt()>=HANDCARD_CNT-2: #一个人几乎全是主牌,可以重开 + # return True + # fen=0 + # p=self.players[i] + # for j in range(HANDCARD_CNT): + # fen+=fenInd[getNum(int(p.cards[j]))] + # if fen<15: + # return True + return False + def printCardsList(self,li,up=-1): + if up<0: + up=len(li) + for i in range(up): + printCard(li[i],i) + print("") + def printAllCards(self): + for i in range(4): + self.players[i].printCards() + print("底牌",end=": ") + self.printCardsList(self.deck[-8:]) + # def getBidAction(self,): + + def CrossShuffle(self):#交叉洗牌 + for i in range(CARDS_CNT // 2-1,-1,-1): # 交叉洗牌 + rnd1, rnd2 = i, i * 2 + self.deck[rnd1], self.deck[rnd2] = self.deck[rnd2], self.deck[rnd1] + + def shuffleDeck(self,T=10): + up=len(self.deck)-1 + self.CrossShuffle() + # print(self.deck) + while(T>0): + self.CrossShuffle() + + for i in range(up, 0, -1): + rnd = random.randint(0, i) # 每次随机出0-i-1之间的下标 + self.deck[i], self.deck[rnd] = self.deck[rnd], self.deck[i] + T-=1 + + + def dealCards(self,beginDeckList,bidFun,lordInfo=(-1,-1,-1)):# + # 发牌,bidFun是叫牌策略 + #setDecor=-1,setNum=-1,setDealer=-1代表 + setDecor, setNum , setDealer=lordInfo[0],lordInfo[1],lordInfo[2] + if beginDeckList is None: + self.shuffleDeck() # 洗牌 + else: + for i in range(len(self.deck)): + self.deck[i]=beginDeckList[i] + # print(self.deck.tolist()) + self.deck_i = 0 + self.currentPlayer=0 + if self.dealer != -1: + self.currentPlayer = (self.dealer + 1) % 4 + for i in range(4): + self.players[i].initCards() + #bidFun(env,round,allActList) + isSet=setDecor!=-1 or setDealer!=-1 or setNum>0 + if isSet:#人为设置,调试用 + self.lordNum = setNum + + print("指定主牌:",decorName[setDecor]+str(setNum)," 庄家:",setDealer) + self.dealer = setDealer + self.bidAns = [toCardIndex(setDecor,self.lordNum)] + self.bidPlayer =setDealer + while (self.deck_i < CARDS_CNT - UNDERCARD_CNT): + self.__dealCard(bidFun) + self.setLord(setDecor, setDealer) + else: + self.bidAns=[] + self.bidPlayer =-1 + while (self.deck_i < CARDS_CNT - UNDERCARD_CNT): + self.__dealCard(bidFun) + if self.bidPlayer!=-1: + if self.dealer==-1: + self.setLord(getDecor(self.bidAns[0]), self.bidPlayer) + else: + self.setLord(getDecor(self.bidAns[0])) + else:# 如果没人叫主,则重新发牌 + # print("重新发牌") + self.dealCards(beginDeckList, bidFun, lordInfo) + for i in range(4): + p=self.players[i] + p.mergeLords() + self.sortPlayerHand(p) + self.setDealerID(self.dealer) + + self.undeck = [] # 存数组 + if self.checkReDealCards() and not isSet: # 满足一定条件可以重开 + # print("重新发牌") + self.dealCards(beginDeckList, bidFun, lordInfo) + + + def __dealCard(self,bidFun):#发牌 + card=self.deck[self.deck_i] + p=self.players[self.currentPlayer] + p.addCard(getKind(card,4,self.lordNum),card)#级牌放入无主里 + allActList=[[]]#空列表代表不叫牌 + for i in range(4): + a=toCardIndex(i, self.lordNum) + n=len(self.bidAns) + if p._lordnumAndKing_cnt[i]==1 and n==0: + allActList.append([a]) + elif p._lordnumAndKing_cnt[i]==2 and n==0: + allActList.append([a]) + allActList.append([a,a]) + elif p._lordnumAndKing_cnt[i]==2 and n==1: + allActList.append([a,a]) + if p._lordnumAndKing_cnt[4]==2 and (len(self.bidAns)<2 or len(self.bidAns)==2 and self.bidAns[0]<53):#对小王可以反无主 + allActList.append([53,53]) + if p._lordnumAndKing_cnt[5]==2 and (len(self.bidAns)<2 or len(self.bidAns)==2 and self.bidAns[0]<54):#对大王可以反无主 + allActList.append([54,54]) + # dfsPrintActList(allActList) + act_id = bidFun(self,p,self.deck_i//4,allActList) + act=allActList[act_id] + self.deck_i += 1 + # self.snatchLord_v0(self.currentPlayer) # 抢主 + + #self.printAllCards() + if len(act) > 0 : + # print("玩家"+str(self.currentPlayer)+"叫了:",end=" ") + dfsPrintActList(act) + self.bidAns=act + self.bidPlayer=self.currentPlayer + self.currentPlayer=(self.currentPlayer+1)%4 + + def setUnderCards(self,discardFun):#换牌, + p = self.players[self.dealer] + self.underCards=[] + self.game_stage = "ambush" + self.currentPlayer = self.dealer + for a in self.deck[-8:]: + p.addCard(getKind(a,self.lordDecor,self.lordNum),a) + self.sortPlayerHand(p) + for i in range(8): + a=discardFun(self,p,i)#选择八张底牌扣下 + self.underCards.append(a) + p.delCard(getKind(a,self.lordDecor,self.lordNum),a) + if len(self.underCards)!=UNDERCARD_CNT: + print("erro",self.underCards) + exit() + # dfsPrintActList(self.underCards) + self.deck[-8:]=self.underCards + self.sortPlayerHand(p) + + for i in range(4): + self.players[i].initCards_orderCards_cnt(self) + self.game_stage = "play" + # for i in range(5): + # env.printCardsList(p.cards_decorList[i], p.cards_decorLen[i]) + # print("") + def setOrder(self):#设置单牌的大小 + self.orderInd = np.zeros((CARDS_CNT+1),dtype='int') + self.decorInd = np.zeros((CARDS_CNT + 1),dtype='int')#花色分类,大小王和级牌都算主牌里 + h=CARDS_CNT//2 + self.orderInd[0]=-1 + self.orderInd[h]=99#大王 + self.orderInd[h-1]= 98 # 小王 + for j in range(4): + for i in range(1,14): + self.orderInd[j*13+i]=i + self.decorInd[j * 13 + i] =j + self.orderInd[j * 13+1] =14#A更大 + for i in range(1, 14):#主牌更大 + self.orderInd[self.lordDecor * 13 + i]=i+40 + self.decorInd[self.lordDecor * 13 + i] = 4 + self.orderInd[self.lordDecor * 13 + 1]= 14+40 # A更大 + for i in range(4):#级牌 + self.orderInd[self.lordNum+i*13]=60 + self.decorInd[self.lordNum+i*13] = 4 + self.orderInd[self.lordNum + self.lordDecor * 13] = 61 + + #紧凑化 + ind=[0]*100 + for i in range(1,h+1): + d=int(self.orderInd[i]) + ind[d]+=1 + k=1 + for i in range(1,100): + if ind[i]>0: + ind[i] = k + k+=1 + for i in range(1,h+1): + self.orderInd[h+i] = self.orderInd[i]=ind[int(self.orderInd[i])] + self.decorInd[h+i] = self.decorInd[i] + self.unlordMax=self.lordDecor==4 and 13 or 12#无主就是13 + + def getDecor(self,id):#id从1开始 + return (id - 1) // 13 + def cmpCard(self,a,b):#比较两组牌大小。返回1是a大,返回0是b大 + if a==0 or b==0 : + return b==0 + # print(self.orderInd) + num1=self.orderInd[a] + num2=self.orderInd[b] + d1, d2 = self.getDecor(a), self.getDecor(b) + if num1<=self.unlordMax and num2<=self.unlordMax:#都不是主 + if d1==d2:#花色相同 + return num1>=num2 + else: + return d1>=d2 + elif num1>self.unlordMax and num2>self.unlordMax: # 都是主牌 + if num1==num2:#地位相同 + return d1>=d2 + else: + return num1>num2 + return num1>self.unlordMax + def _sortCardList_cmp(self,a,b):#比较两组牌大小。返回1是a大,返回-1是b大 + if self._ind[a]>1 and self._ind[b]>1 or self._ind[a]==1 and self._ind[b]==1: + num1 = self.orderInd[a] + num2 = self.orderInd[b] + if num1<=self.unlordMax and num2<=self.unlordMax:#都不是主牌 + if self.getDecor(a) == self.getDecor(b): # 花色相同 + return num1 >= num2 and -1 or 1 + return self.getDecor(a) > self.getDecor(b) and -1 or 1 + return num1>=num2 and -1 or 1 + elif self._ind[a]>1: + return -1 + return 1 + def _sortCardList_cmp1(self,a,b):#比较两组牌大小。返回1是a大,返回0是b大 + return (self.cmpCard(a,b) and -1 or 1) + def _sortCardList_cmp2(self,a,b):#比较两组牌大小。把拖拉机排序 + lena = len(a) + lenb = len(b) + num1 = self.orderInd[a[0]] + num2 = self.orderInd[b[0]] + if lena==lenb: + if num1<=self.unlordMax and num2<=self.unlordMax:#都不是主牌 + if self.getDecor(a[0]) == self.getDecor(b[0]): # 花色相同 + return num1 >= num2 and -1 or 1 + return self.getDecor(a[0]) > self.getDecor(b[0]) and -1 or 1 + return num1>=num2 and -1 or 1 + return lena>lenb and -1 or 1 + def sortCardList2(self,actList):#对卡牌排序,找出几个拖拉机,几个对子,几个单牌,会重叠,并且要求是相同颜色 + self._ind=np.zeros((CARDS_CNT2+1)) + n=len(actList) + cnt2 = 0 + for i in range(n): + self._ind[actList[i]]+=1 + if self._ind[actList[i]]==2: + cnt2+=1 + actList = sorted(actList, key=cmp_to_key(self._sortCardList_cmp)) + traList=[] + doubleList = [] + prePos=-1 + for i in range(cnt2-1):#找拖拉机 + next_i=(i+1)*2 + s = self.orderInd[actList[i * 2]] - self.orderInd[actList[next_i]] + if (s == 0 or s== 1): + if prePos==-1: + prePos=i*2 + elif prePos!=-1: + traList.append([actList[t] for t in range(prePos,i*2+2,2)]) + prePos=-1 + doubleList.append(actList[i*2]) + if cnt2!=0: + if prePos != -1 : + traList.append([actList[t] for t in range(prePos,cnt2*2,2)]) + doubleList.append(actList[(cnt2-1) * 2]) + traList = sorted(traList, key=cmp_to_key(self._sortCardList_cmp2)) + actList = sorted(actList, key=cmp_to_key(self._sortCardList_cmp1)) + return actList,doubleList,traList + def sortCardList1(self,actList,doubleList,traList):#对卡牌排序,找出几个拖拉机,几个对子,会把不同类别区分开(去重)//,要提前调用sortCardList2 + cnt2=len(doubleList) + ind=np.zeros(55,dtype=int) + for tra in traList: + for a in tra: + ind[a]+=1 + for a in doubleList: + ind[a] += 1 + doubleList1 = [] + actList1=[] + for a in actList: + if ind[a]==0: + actList1.append(a) + for a in doubleList: + if ind[a]==1: + doubleList1.append(a) + return actList1,doubleList1,traList + def _isLord(self,actList): + for a in actList: + if self.orderInd[a]<=self.unlordMax: + return False + return True + def isLord(self,act:Action): + if self._isLord(act.one) == False: + return False + for dou in act.double: + if self._isLord(dou)==False: + return False + return True + def getActKind(self,act:Action):#获得一个动作类别,如果什么都有或者为空返回INF + kind=INF + for a in act.one: + k=getKind(a,self.lordDecor,self.lordNum) + if kind!=INF and kind!=k: + return INF + kind=k + for dou in act.double: + for a in dou: + k = getKind(a, self.lordDecor, self.lordNum) + if kind != INF and kind != k: + return INF + kind = k + return kind + def _useCmpCards(self,act1:Action,act2:Action):#必须二者是同一种类别!!! + n=len(act1.double) + for i in range(n):#先判断2是否为对子 + for j in range(0,len(act1.double[i]),2): + if act2.double[i][j]!=act2.double[i][j+1]: + return True + if n>0:#有对子或者拖拉机,就看对应牌的大小 + for i in range(n): # 先判断act2是否为拖拉机 + tra1=act1.double[i] + tra2=act2.double[i] + for j in range(2, len(tra2), 2): + if abs(self.orderInd[tra2[j]]- self.orderInd[tra2[j-2]])>1:#是相邻的 + return True + if self.orderInd[tra1[0]] >= self.orderInd[tra2[0]]:#只有前面的牌比后面的牌大,就返回True + return True + return False + for i in range(len(act1.one)): # 最后判断单牌 + if self.orderInd[act1.one[i]] != self.orderInd[act2.one[i]]: # 单牌且不等 + return self.cmpCard(act1.one[i], act2.one[i]) + return True # 完全想等,先出的大 + + + def useCmpCards(self,act1:Action,act2:Action):#比较两组牌大小,保证两组牌一样多,且actList1为先出,actList2为后出。返回1是actList1大,否则actList2大 + n=act1.len + k1 = self.getActKind(act1)#判断什么类型 + k2 = self.getActKind(act2) + if k1==self.lordDecor and k2==self.lordDecor:#如果都是主牌,先判断拖拉机,在判断单对子 + return self._useCmpCards(act1,act2) + elif k1!=self.lordDecor and k2!=self.lordDecor:#如果都是非主牌,判断花色,花色相同比大小,否则先出的大。 + if k2!=k1:#act2是杂牌 + return 1 + return self._useCmpCards(act1,act2) + elif k1==self.lordDecor:#先出的是主牌,后出的不是,先出的大 + return 1 + else:#先出的不是主牌,后出的是主牌,要看主牌能否完全管上先出的。这里先出的一定是同一花色。主牌要杀拖拉机,只需要相应数量对子。 + for i in range(len(act1.double)): # 先判断2是否为对子或拖拉机 + for j in range(0, len(act1.double[i]), 2): + if act2.double[i][j] != act2.double[i][j + 1]: + return 1 + return 0 + + # def judgeRoundWin(self): + def getOrderID(self,a):#从0开始,非主牌是[0,11],主牌[0,15] + k = self.orderInd[int(a)] + if k > self.unlordMax: + k -= self.unlordMax + return int(k-1) + + def getMaxCards_cmp1(self,a): + return a==2 + def getMaxCards_cmp2(self,a): + return a>0 + def getMaxCards(self,p:Player): # 返回已知的玩家手里最大的牌,来判断先手玩家是否可以甩牌,sortCardsList2是一名玩家分好类的手牌,且会相互包含 + li=[] + sortCardsList2=p.toSortCardsList2(self) + for kind in range(5): + if kind==self.lordDecor:#主牌不能甩 + continue + k1 = p.getSelfMaxCard(kind, self.getMaxCards_cmp1)#从大到小找到第一个没有出过2张牌的牌的顺序号 + k2 = p.getSelfMaxCard(kind, self.getMaxCards_cmp2)#从大到小找到第一个出过0张牌的牌的顺序号 + act=Action() + for a in sortCardsList2[kind][0]: + k=self.getOrderID(a) + if k>=k1:#看比它大的有多少已经出过2张,如果都出过2张,说明它是最大的牌 + act.addOne(a) + for a in sortCardsList2[kind][1]:#看是不是最大的对子 + k = self.getOrderID(a) + if k>=k2:#看有多少出了0张,如果都没有出了0张 + act.addDou([a,a]) + maybeTra=[] + seqCnt=0 + + for i in range(p.orderCards_len[kind] - 1, -1,-1):#寻找所有可能的拖拉机 + if i == p.lordNumOrder: + c=0 + for j in range(4): + if j != p.lordDecor: + c+=p.lordNumSee_cnt[j]==0 + seqCnt+=c + if c<3 and seqCnt>0: + if seqCnt>1: + maybeTra.append((i,seqCnt)) + # maxSeqCnt = max(maxSeqCnt, seqCnt) + seqCnt = 0 + elif p.orderCards_cnt[kind][i]==0: + seqCnt+=1 + elif seqCnt>0: + if seqCnt > 1: + maybeTra.append((i+seqCnt,seqCnt)) + seqCnt = 0 + if seqCnt>1: + maybeTra.append((-1+seqCnt,seqCnt)) + # if p.id == 0: + # print(maybeTra) + for traList in sortCardsList2[kind][2]:#看自己的拖拉机 + n=len(traList) + k = self.getOrderID(traList[0]) + for tra in maybeTra: + # if p.id == 0: + # print(tra, n) + if tra[1]>=n and tra[0]>k:#可能的拖拉机的长度大于等于自己的拖拉机,且可能的拖拉机比我方的次序大。 + break + else:#正常结束循环,说明我方的拖拉机最大 + act.addDou([traList[i//2] for i in range(len(traList)*2)]) + if act.isSeq(): + li.append(act) + return li#返回一个5元列表,代表一定可以甩的牌、 + def updatesortCardList2(self): + self.sortCardsList2=[] + for i in range(4): + li=self.players[i].toSortCardsList2(self) + self.sortCardsList2.append(li) # 会重叠 + def _judgeSeqUse(self,act:Action):# + # 是否为甩牌,是否可以出 + self.updatesortCardList2() + sortCardList4=self.sortCardsList2 + if act.isSeq():#是甩牌 + mina=INF + card=0 + for a in act.one:#找出最小的牌 + if mina>self.orderInd[a]: + mina=self.orderInd[a] + card=a + kind, num = getKindAndNum(card,self.lordDecor,self.lordNum) + if card>0: + for pid in range(4): + if pid != act.playerId: + li=sortCardList4[pid] + if len(li[kind][0])>0 and self.orderInd[li[kind][0][0]]>self.orderInd[card]:#寻找这个花色所有比card顺序大的牌: + return True,False + + for pid in range(4): + if pid != act.playerId:#遍历每个人手牌的对子。 + li = sortCardList4[pid] + for dou in act.double: + kind, num = getKindAndNum(dou[0], self.lordDecor, self.lordNum) + lendou=len(dou)#它是li里面拖拉机牌数的2倍,比如3344,则li里面表示为34 + if lendou==2: + a=li[kind][1] + if len(a) > 0 and self.orderInd[a[0]] > self.orderInd[dou[0]]: # 寻找这个花色所有比card顺序大的牌: + return True, False + else: + tractors = li[kind][2] + for tra in tractors: + if len(tra)*2>=lendou and self.orderInd[tra[0]] > self.orderInd[dou[0]]: + return True, False + return True, True + return False,True + + def firstPolicy(self,firstPlayerId,firstPolicyFun):#第一个人出牌,policyFun是一个回调,代表如果有甩牌,甩牌的出牌策略 + self.currentPlayer =firstPlayerId + p=self.players[firstPlayerId] + allActList=self.getFirstAllAction(p) + act_id=firstPolicyFun(self,p,Action([],[],playerId=p.id),allActList) + act = allActList[act_id] + if isinstance(act,Action) :#act是action代表要使用的动作 + # act.println() + p.useAction(self,act) + return act + else:#act是list代表要甩牌的类 + kind=act[0] + pHandCards=p.cards[kind] + cardsList=[] + act=Action([],[],playerId=p.id) + for i in range(len(pHandCards)): + allActList=[] + if i>=2: + allActList.append([]) + for i in range(len(pHandCards)): + a=pHandCards[i] + allActList.append([a]) + act_id=firstPolicyFun(self,p,act,allActList) + if len(allActList[act_id])==0: + break + p.useCardsList(self, allActList[act_id]) + cardsList.append(allActList[act_id][0]) + act = cardsListToAction(self, p.id, cardsList) + + + isSeq, canSeq = self._judgeSeqUse(act) + if isSeq and canSeq == False: # 如果不能甩 + # print("不能甩!!!") + if self.players[act.playerId].dealerTag < 2: # 是庄家甩牌失败 + self.sumSc += 10 + else: + self.sumSc = max(0, self.sumSc - 10) + p.addCardsList(self, cardsList) + act= act.getMinCard(self) + p.useAction(self, act) + return act + + def getFirstAllAction(self,p:Player):#返回作为先出玩家的所有可能的动作(不包含甩牌),sortCardsList1是一名玩家分好类的手牌,且会相互包含 + ans=[] + sortCardsList2= p.toSortCardsList2(self) # 会相互包含 + # maxCards=self.getMaxCards(sortCardsList1,p) + for i in range(5): + for a in sortCardsList2[i][0]: + ans.append(Action([a],playerId=p.id)) + for a in sortCardsList2[i][1]: + ans.append(Action([],[[a,a]],playerId=p.id)) + for tractor in sortCardsList2[i][2]:#拖拉机 + ltr=len(tractor) + for begin_i in range(ltr): + tractor_tmp = [tractor[begin_i],tractor[begin_i]] + for begin_j in range(begin_i+1,ltr): + tractor_tmp.append(tractor[begin_j]) + tractor_tmp.append(tractor[begin_j]) + ans.append(Action([],[tractor_tmp.copy()],playerId=p.id)) + for i in range(4): + if i!=self.lordDecor and len(p.cards[i])>1: + ans.append([i]) + return ans + def getAllAct_dfs(self,doubleList,doubleList_i,ansDown,nowList,nowList_i,n): + if nowList_i==n: + ansDown.append(nowList.copy()) + return + doubleList_len=len(doubleList) + for i in range(doubleList_i,doubleList_len): + if doubleList_len-i 0 + ansDown=self.getFollowAct_one(p,firstAct[0],kind) + if kind == self.lordDecor or c0 > 0: # 是主牌或副牌,但有这类副牌 + for i in range(c0): + a = toSortCardsList2[kind][0][i] + if self.orderInd[a] > self.orderInd[firstAct[0]]: + ansUp.append([a]) + else: # 用主牌杀 + for i in range(len(p.cards[self.lordDecor])): + a = p.cards[self.lordDecor][i] + ansUp.append([a]) + elif n == 2: + isHave = c1>0 + if kind == self.lordDecor or c1>0: # 这类牌有对子 + for a in toSortCardsList2[kind][1]: + if self.orderInd[a] > self.orderInd[firstAct[0]]: + ansUp.append([a,a]) + elif len(p.cards[kind]) == 0: # 用主牌杀 + for a in toSortCardsList2[self.lordDecor][1]: + ansUp.append([a,a]) + if c1>0: + for a in toSortCardsList2[kind][1]: + if self.orderInd[a] <= self.orderInd[firstAct[0]]: + ansDown.append([a, a]) + else: + ansDown.append([INF ,INF]) #比如先手出了66,而自己有7,则此时依然返回INFINF + else:#拖拉机 + #较大的拖拉机和较小的拖拉机 + if kind == self.lordDecor or c2>0: # 这类牌有拖拉机 + for tractor in toSortCardsList2[kind][2]: + for j in range(len(tractor)-n2+1):#比如最大的拖拉机是3344,而自己手里又55667788,则此时有3种出牌方式管上它 + isHave = True + if self.orderInd[tractor[j]]>self.orderInd[firstAct[0]]: + ansUp.append([tractor[j+k//2] for k in range(n)]) + else: + ansDown.append([tractor[j + k // 2] for k in range(n)]) + elif len(p.cards[kind]) == 0: # 用主牌杀 + for tractor in toSortCardsList2[self.lordDecor][2]: + for j in range(len(tractor)-n2+1): + ansUp.append([tractor[j+k // 2] for k in range(n)]) + + if not isHave:#如果没有同样长度的拖拉机,则从对子里出 + tempLi = [] + if len(toSortCardsList2[kind][1]) > n2: + self.getAllAct_dfs(toSortCardsList2[kind][1], 0, ansDown, tempLi, 0, n2) # 从对子里选取所有组合 + else: + for a in toSortCardsList2[kind][1]: + tempLi.append(a) + tempLi.append(a) + tempLi_len = len(tempLi) + for i in range(tempLi_len, n): + tempLi.append(INF) + # 剩余牌优先从这个颜色里选取 + #没有这个颜色再从其他任意牌选 + #比如先手是'<♥A>', '<♥A>', '<♥K>', '<♥K>', '<♥Q>', '<♥Q>',我有JJQQ9,此时会返回JJQQINFINF + ansDown.append(tempLi) + + return ansUp,ansDown,isHave + + def getFollowAct_one(self, p: Player, card_max, kind): # 返回小于某张牌的全部牌 + ans=[] + if len(p.cards[kind])>0:#大于0 + for i in range(len(p.cards[kind])): + a=p.cards[kind][i] + if self.orderInd[a]<=self.orderInd[card_max]: + ans.append([a]) + else:# + for i in range(5): + if i!=self.lordDecor: + for j in range(len(p.cards[i])): + ans.append([p.cards[i][j]]) + return ans + + def __otherPolicy(self, p:Player,firstAct:Action,usedAct,cards, otherPolicyFun): + allActList_up,allActList_down,isHave = self.getAllAction(p, cards) + allActList=allActList_down+allActList_up + act_id = otherPolicyFun(self, p, usedAct, allActList) + act_ans = allActList[act_id] + firstKind=firstAct.getKind(self) + doulasti=len(usedAct.double)-1 + p.useCardsList(self, act_ans) + usedList=[] + for i in range(len(act_ans)): + if act_ans[i]==INF: + if len(p.cards[firstKind])==0:#没有这个类别的牌就从其他牌里选 + allActList_1 = p.otherKindCards(firstKind) + else:#从这个类别里选 + allActList_1=p.cards[firstKind].copy() + act_id_1 = otherPolicyFun(self, p, usedAct, allActList_1) + # dfsPrintActList(allActList_1) + # print(act_id_1,act_ans) + act_ans[i]=allActList_1[act_id_1] + usedList.append(act_ans[i]) + p.useCardsList(self, [act_ans[i]]) + usedAct.setDou(doulasti, usedList.copy()) # 设置牌 + return act_ans + def otherPolicy(self,act4,firstPlayerID,nowPlayerId,otherPolicyFun): + self.currentPlayer = nowPlayerId + # 第一个人出牌,otherPolicyFun是一个回调,代表如果跟牌太多, + p = self.players[nowPlayerId] + firstAct=act4[firstPlayerID] + act_ans = Action([], [], playerId=p.id) + for a in firstAct.one: + li = self.__otherPolicy(p,firstAct,act_ans,[a],otherPolicyFun) + act_ans.add(li) + for dou in firstAct.double: + act_ans.addDou([]) + li=self.__otherPolicy(p, firstAct, act_ans, dou, otherPolicyFun) + act_ans.setDou(len(act_ans.double)-1, li) # 设置牌 + return act_ans + + def step(self,actList4,first_playerId):#输入4个人的出牌组合,保证每个人出的牌数量一样多,返回赢得那个人和所得分数 + playerId=first_playerId + actList4[playerId].sort(self)# + for i in range(1,4): + k=(first_playerId+i)%4 + actList4[k].sort(self)# + if self.useCmpCards(actList4[playerId],actList4[k])==0:#actList4[k]大 + playerId=k + self.round_i+=1 + #找找到最大的那个人 + sc=0 + n=actList4[first_playerId].len#所有人的牌数量都是n + for i in range(4): + sc+=actList4[i].getFen() + if self.players[playerId].dealerTag>1: + self.sumSc+=sc + leftCardsCnt=0 + for i in range(4): + leftCardsCnt+= self.players[i].len + if leftCardsCnt==0:#游戏结束,结算底牌分数 + sc_under = rate=0 + # if self.players[playerId].dealerTag > 1: # 不是庄家赢 + for a in self.underCards: # 结算底牌分数 + num = getNum(a) # 点数,[1,13]王是14 + sc_under += fenInd[num] # 分数 + rate = 2 + if n > 1: # 判断倍数 + doubleCnt = actList4[playerId].getDouleCnt() * 2 + if doubleCnt > 0: # 每有一个对子,倍数+2 + rate = 2 + doubleCnt + else: # 普通甩牌没有对子,倍数+1 + rate = 2 + 1 # 普通甩牌为3倍 + if self.players[playerId].dealerTag>1:#不是庄家赢 + self.sumSc += sc_under*rate + self.undeck+=self.underCards + self.isTer =True + return playerId, sc,self.isTer,sc_under*rate + return playerId,sc,False,0#返回赢得玩家id,本轮得分,是否结束游戏,结算信息 + def getGrade(self,sc): + if sc<80: + return (sc==0 and 0 or (sc//40+1))-3#判断庄家升几级 + else: + return sc // 40-2#如果不足120仅仅换庄,不升级 + def setLord(self,kind,playerID=-1): + self.lordDecor=kind + if playerID!=-1: + self.dealer=playerID + for i in range(4): + self.players[i].setLord(kind,self.lordNum) + self.setOrder() + def getLord(self): + return self.lordNum+self.lordDecor*13 + + def sortPlayerHand(self,player): + for i in range(5): + player.cards[i].sort(key=cmp_to_key(self._sortCardList_cmp1)) + + def _getActListMaxCmp(self,a:Action,b:Action): + if a.len==b.len: + la,lb=a.getDouleCnt(),b.getDouleCnt() + if la==lb and la==0: + return self.orderInd[b.one[0]]-self.orderInd[a.one[0]] + elif la==lb: + for i in range(min(len(a.double),len(b.double))): + if len(a.double[i])!=len(b.double[i]): + return len(b.double[i])-len(a.double[i]) + else: + return self.orderInd[b.double[0][0]]-self.orderInd[a.double[0][0]] + return lb-la + return b.len-a.len + def getActListMax(self,allActionList):#从动作列表里寻找最大的动作 + for act in allActionList: + act.sort(self) + allActionList.sort(key=cmp_to_key(self._getActListMaxCmp)) + return allActionList[0] + + + def printAllInfo(self,nowpid=None, act=None): # act代表4个人每个人出的牌,类型是Action + if nowpid!=None: + print("当前玩家:"+str(nowpid), end=",") + print("双方等级", self.levelOfBoth,end=",") + print("主牌:", decorName[self.lordDecor] + str(self.lordNum), " 庄家:", self.dealer) + self.printAllCards() + if act!=None and isinstance(act[0],Action): + for i in range(4): + act[i].println(i) + print("当前闲家的分" + str(self.sumSc)) + def printUnderCards(self): # act代表4个人每个人出的牌 + self.printCardsList(self.deck[-8:]) + def getPlayNowGrade(self,playerId): + g=self.levelOfBoth[playerId%2] + if g==1: + g=14 + return g \ No newline at end of file diff --git a/rlcard/games/tractor/tractor_gameUtil.py b/rlcard/games/tractor/tractor_gameUtil.py new file mode 100644 index 00000000..e51f162e --- /dev/null +++ b/rlcard/games/tractor/tractor_gameUtil.py @@ -0,0 +1,171 @@ +import math +import random +from functools import cmp_to_key +import numpy as np + + +CARDS_CNT=108 +CARDS_CNT2=54 +UNDERCARD_CNT=8 +INF=1000 +HANDCARD_CNT = (CARDS_CNT - UNDERCARD_CNT) // 4 +fenInd=[0,0,0,0,0,5,0,0,0,0,10,0,0,10,0,0,0,0,0,0] +decorName=["♠","♥","♣","♦","王"] +NameTodecorId={"♠":0,"♥":1,"♣":2,"♦":3,"王":4} +numName=["","A","2","3","4","5","6","7","8","9","10","J","Q","K"] + +def getKind(id,dec,num): # id从1开始,判断类别,会把级牌和王算进主牌里 + if id<1: + return 0 + d = (id - 1) // 13 # 黑桃是0,红糖是1,梅花是2,方片是3,王是4 + n= (id - 1) % 13 + 1 # 点数 + if d==4 or n==num: + return dec + return d +def getDecor(id): # id从1开始,判断花色 + return int((id - 1) // 13) +def getNum(id): # id从1开始,判断点数 + return int((id - 1) % 13+1) +def getDecorAndNum(id): + if id<1: + return 0,0 + decor = (id - 1) // 13 # 黑桃是0,红糖是1,梅花是2,方片是3,王是4 + num = (id - 1) % 13 + 1 # 点数 + return int(decor),int(num) +def getKindAndNum(id,dec,num): + if id<1: + return 0,0 + d = (id - 1) // 13 # 黑桃是0,红糖是1,梅花是2,方片是3,王是4 + n = (id - 1) % 13 + 1 # 点数 + if d==4 or n==num:## 黑桃是0,红糖是1,梅花是2,方片是3,王是4 + return dec,num + return int(d),int(n) +def toCardIndex(decor,num): + return num+decor*13 +def cardToString(id,up=False):# + decor, num = getDecorAndNum(id) + if id == INF: + return "<任意>" + elif num == 0: + return "" + elif decor == 4: + return "<"+ (num == 1 and "小" or "大") + "王>" + else: + return "<" + decorName[decor] + numName[num] + ">" +stringToCardId={} +def initStringToCard(): + stringToCardId["INF"]=INF + stringToCardId["大王"] = 54 + stringToCardId["小王"] = 53 + for i in range(1,CARDS_CNT2+1): + stringToCardId[cardToString(i)]=i + stringToCardId[cardToString(i).lower()] = i + stringToCardId[cardToString(i)[1:-1]] = i + stringToCardId[str(i)] = i +initStringToCard() + +def printCard(id,i=0): + decor,num=getDecorAndNum(id) + if id==INF: + print("", end=" ") + elif num==0: + print("", end=" ") + elif decor==4: + print("", end=" ") + else: + print("",end=" ") +def printCardln(id,i=0): + printCard(id,i) + print("") +def snatchLord_v0(env,p,round,allActList):#发牌时候抢主的常规策略 + if env.lordDecor>=0: + return -1 + + for i in range(4): + p=env.players[id] + c=p.cards_decorLen[i]+p.cards_decorLen[4] + + if c>=0 :#没人亮过,超过均值就亮牌,且牌数大于等于4,主牌一共36个,平局每人9个 + for j in range(p.cards_decorLen[4]): + decor,num=getDecorAndNum(p.cards_decorList[4][j]) + if num ==env.lordNum and decor==i: # 有级牌 + return i + return -1 +def randomUpdateINF(roundId,p,act:list, kind):#随机选取动作,kind是第一个出牌的玩家的花色。返回种类和在cards_decorList中位置的编号 + actList=[] + for j in range(p.cards_decorLen[kind]):#先看本花色有木有 + if p.cards_decorList[kind][j]!=0: + actList.append((kind,j)) + if len(actList)==0:#本花色没有,去其它花色找 + for i in range(5): + if i==kind or p.cards_decorLen[i]==0: + continue + for j in range(p.cards_decorLen[i]): + if p.cards_decorList[i][j] != 0: + actList.append((i,j)) + ans=actList[random.randint(0,len(actList)-1)] + return ans[0],ans[1] +def getAllAct(self, sortCardsList, p, cardsList_max, kind): # sortCardsList是p玩家分好类的手牌 + # 返回所有大过之前最大的玩家的牌和小于之前玩家的牌 + # cardsList_max只能是单张,对子,连对 + # 小于玩家的牌有太多组合会被忽略 + n = len(cardsList_max) + ansUp = [] + ansDown = [] + if n == 1: # 单张牌 + ansDown = self.getFollowAct_one(p, cardsList_max[0], kind) + if kind == self.lordDecor or p.cards_decorLen[kind] > 0: # 是主牌或副牌,但有这类副牌 + for i in range(p.cards_decorLen[kind]): + a = p.cards_decorList[kind][i] + if self.orderInd[a] > self.orderInd[cardsList_max[0]]: + ansUp.append([a]) +def getActListFen(actList:list):#得到动作列表的所有分 + ans=0 + for act in actList: + ans+=act.getFen() + return ans +def searchFen(cardList): + ans=0 + for a in cardList: + num=getNum(a) + ans+=fenInd[num] + return ans + +# env=CC() +# env.dealCards(None,0,5,0)#发牌测试 +# # env.dfsPrintActList([9,27]) +# act1=Action([],[[4,4,6,6],[10,10]]) +# act2=Action([],[[8,8,9,9],[11,11]]) +# print(env.useCmpCards(act1,act2)) +# +# +# +# +# actList,doubleList,traList=env.sortCardList2([11,11,12,12,13,13,14,1,2,3,4,5,5,6,15,6,8,8,9,9]) +# print(actList,doubleList,traList) +# ansDown=[] +# nowList=[] +# begin_time = time.time() +# env.getAllAct_dfs(doubleList,0,ansDown,nowList,0,4) +# passed_time = time.time() - begin_time +# print(ansDown) +# print(passed_time) +# li=[39, 39, 23, 12, 26, 5, 53, 1, 38, 30, 46, 54, 48, 40, 36, 6, 28, 46, 26, 18, 7, 16, 27, 5, 22, 20, 47, 41, 41, 34, 8, 3, 31, 30, 13, 16, 23, 15, 48, 13, 51, 4, 37, 44, 33, 25, 52, 34, 9, 37, 21, 3, 17, 50, 29, 24, 51, 49, 38, 35, 43, 24, 6, 18, 32, 22, 29, 7, 20, 11, 19, 15, 36, 14, 42, 27, 45, 14, 12, 50, 45, 52, 31, 11, 42, 40, 47, 33, 54, 32, 8, 21, 10, 49, 9, 25, 53, 44, 1, 4, 17, 19, 10, 2, 35, 43,28,2] +# env.dealCards()#发牌测试 +# env=CC() +# env.dealCards() +# # actList,doubleList,traListt=env.sortCardList2(li) +# # print(env.dfsPrintActList(actList)) +# li=np.zeros(33,dtype=np.int32) +# li[0]=2 +# li = sorted(li, key=cmp_to_key(env._sortCardList_cmp1)) +# env.dfsPrintActList(li) +# print(env.orderInd) +# actList, doubleList, traListt =env.sortCardList2([1,3,53,53,5,5,31,31,3,4,4,6,6,8,8,11,12,1]) +# env.dfsPrintActList(actList) +# env.dfsPrintActList(doubleList) +# env.dfsPrintActList(traListt) + + + + diff --git a/rlcard/games/tractor/tractor_player.py b/rlcard/games/tractor/tractor_player.py new file mode 100644 index 00000000..f7c356aa --- /dev/null +++ b/rlcard/games/tractor/tractor_player.py @@ -0,0 +1,171 @@ +import math +import random +from functools import cmp_to_key + +import numpy as np + +from tractor_gameUtil import HANDCARD_CNT, CARDS_CNT2, printCard, getNum, getDecor, getKindAndNum, getKind, \ + getDecorAndNum + + +class Player(): + def __init__(self,id): + self.id=id + self.dealerTag=0 #0是庄家,1是庄家跟班,23是闲家 + self.initCards() + def initCards(self): + self.cards= [[] for i in range(5)] # 手牌 + self.uncards = [[] for i in range(5)] # 已经出的牌 + self.cards_lord=[]# 主牌 + self.len=0 + self.unlen = 0 + self._lordnumAndKing_cnt=np.zeros(6,dtype='int')# 自己所有牌中的级牌和王的数量,级牌是0,1,2,3.小王在4,大王在5 + + def initCards_orderCards_cnt(self,env):#初始化orderCards_cnt + z0 = np.zeros(13 + 5, dtype='int') + self.orderCards_cnt=[z0.copy(), z0.copy(), z0.copy(),z0.copy(), z0.copy()] + # orderCards_cnt[i][j]代表类别i的手牌中,顺序是j的有多少个。它包含了所有当前玩家可见的牌 + self.orderCards_len=[0, 0, 0, 0, 0]# 别人已经出的手牌数量 + self.lordNumSee_cnt=np.zeros(4,dtype='int')# 可见级牌数量 + + if self.lordDecor==4: + self.orderCards_len[4]=3#无花色情况下主牌有12张,顺序有3个 + for i in range(4): + self.orderCards_len[i]=13 + else: + for i in range(4): + self.orderCards_len[i] = 12 + self.orderCards_len[self.lordDecor] += 4 # 大小王级牌和主级牌 + self.lordNumOrder=env.getOrderID(54)-3#主级牌的顺序 + for i in range(5): + for j in range(len(self.cards[i])): + a=self.cards[i][j] + if self.isLevelCard(a):#是级牌 + self.lordNumSee_cnt[getDecor(a)]+=1 + k=env.getOrderID(a) + # print(a,k) + self.orderCards_cnt[i][k]+=1 + if self.dealerTag==0: + for a in env.underCards: + k=env.getOrderID(a) + kind,num=getKindAndNum(a,self.lordDecor,self.lordNum) + self.orderCards_cnt[kind][k] += 1 + if self.isLevelCard(a): + self.lordNumSee_cnt[getDecor(a)]+=1 + def isLevelCard(self,a): + return getNum(a)==self.lordNum and a<53 + def addCard(self,kind,card): + self.cards[kind].append(card) + self.len+=1 + decor,num=getDecorAndNum(card) + if num==self.lordNum: + self._lordnumAndKing_cnt[decor]+=1 + elif decor==4: + self._lordnumAndKing_cnt[num-1+4] += 1 + + def delCard(self,kind,card): + self.cards[kind].remove(card) + self.len-=1 + def delCards(self,cards): + for a in cards: + kind=getKind(a,self.lordDecor,self.lordNum) + self.cards[kind].remove(a) + self.len-=len(cards) + def mergeLords(self):#把级牌和大小王放入主牌所在花色,如果是无主则什么都不做 + if self.lordDecor!=4: + self.cards[self.lordDecor]+=self.cards[4] + self.cards[4]=[] + def group(self):#0是庄家,1是闲家 + return self.dealerTag//2 + def isDealer(self):#是否为庄家 + return self.dealerTag<2 + def setNum(self,num): + self.lordNum = num + def setLord(self,decor,num): + self.lordNum = num + self.lordDecor = decor + self.cards_lord = self.cards[decor] + def getLordCnt(self): + return len(self.cards[self.lordDecor]) + def getSelfMaxCard(self,decor,cmp):#cmp是比较函数,返回i,是牌的次序号,代表大于i的牌都满足cmp,i是从大到小第一个不满足cmp的牌顺序的编号。 + # 该函数返回自己牌以及自己见到的牌当中,某种花色从大到小第一张不满足cmp的顺序 + for i in range(self.orderCards_len[decor]-1,-1,-1): + if i==self.lordNumOrder: + for j in range(4): + if j!=self.lordDecor and not cmp(self.lordNumSee_cnt[j]): + return i + elif not cmp(self.orderCards_cnt[decor][i]): + return i + return -1 + def updateSortCardsList(self,env): + self.sortCardsList2=self.toSortCardsList2(env) + self.sortCardsList1=self.toSortCardsList1(self.sortCardsList2,env) + def toSortCardsList1(self,sortCardList2,env):#去重 + li=[] + for i in range(5): + # oneList, doubleList, traList = env.sortCardList1(self.cards_decorList[i]) + li.append((env.sortCardList1(sortCardList2[i][0],sortCardList2[i][1],sortCardList2[i][2]))) + return li + def toSortCardsList2(self,env):#相互包含 + li=[] + for i in range(5): + # oneList, doubleList, traList = env.sortCardList1(self.cards_decorList[i]) + li.append((env.sortCardList2(self.cards[i][0:len(self.cards[i])]))) + return li + def printCards(self): + print("玩家{}".format(self.id),end=" ") + k=0 + for i in range(5): + for j in range(len(self.cards[i])): + printCard(self.cards[i][j],k) + k+=1 + print("") + def addCardsList(self,env,cards): + cnt=0 + for a in cards: + if a<=0 or a>54: + continue + cnt+=1 + order = env.getOrderID(a) + kind, num = getKindAndNum(a,self.lordDecor,self.lordNum) + decor=getDecor(a) + self.uncards[kind].remove(a) + self.cards[kind].append(a) + env.undeck.remove(a) + for i in range(4): + if i != self.id: + env.players[i].orderCards_cnt[kind][order] -= 1 + if num == self.lordNum and decor < 4: + env.players[i].lordNumSee_cnt[decor] -= 1 + self.len+=cnt + self.unlen-=cnt + def useCardsList(self,env,cards): + cnt=0 + for a in cards: + if a<=0 or a>54: + continue + cnt+=1 + order = env.getOrderID(a) + kind, num = getKindAndNum(a,self.lordDecor,self.lordNum) + decor = getDecor(a) + self.cards[kind].remove(a) + self.uncards[kind].append(a) + env.undeck.append(a) + for i in range(4): + if i != self.id: + env.players[i].orderCards_cnt[kind][order] += 1 + if num == self.lordNum and decor < 4: + env.players[i].lordNumSee_cnt[decor] += 1 + self.len-=cnt + self.unlen+=cnt + def useAction(self,env,act): + self.useCardsList(env, act.one) + for dou in act.double: + self.useCardsList(env, dou) + + def otherKindCards(self, kind): # 获取所有kind以外类别的牌 + ans = [] + for i in range(5): + if i!=kind: + ans += self.cards[i].copy() + return ans diff --git "a/rlcard/games/tractor/\345\217\214\345\215\207\345\274\272\345\214\226\345\255\246\344\271\240\347\216\257\345\242\203\344\275\277\347\224\250\350\257\264\346\230\216.docx" "b/rlcard/games/tractor/\345\217\214\345\215\207\345\274\272\345\214\226\345\255\246\344\271\240\347\216\257\345\242\203\344\275\277\347\224\250\350\257\264\346\230\216.docx" new file mode 100644 index 00000000..f8fa2d5f Binary files /dev/null and "b/rlcard/games/tractor/\345\217\214\345\215\207\345\274\272\345\214\226\345\255\246\344\271\240\347\216\257\345\242\203\344\275\277\347\224\250\350\257\264\346\230\216.docx" differ