Skip to content

Instantly share code, notes, and snippets.

@KireinaHoro
Created May 19, 2018 08:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save KireinaHoro/bb028d06031ba4a3de2eff3b8aed48b1 to your computer and use it in GitHub Desktop.
Save KireinaHoro/bb028d06031ba4a3de2eff3b8aed48b1 to your computer and use it in GitHub Desktop.
FightTheLandlord
#!/usr/bin/env python
# FightTheLandlord Artificial Stupidity
# Version 0
# 2018 Pengcheng Xu, Junjie Shan, Zixin Zhou
# All rights reserved.
# TODO 判斷是用大牌打地主還是清理散牌
# TODO 給牌型排序(設計估值函數)
# TODO 計算打出牌型的分值
# TODO 儘量選長的牌出
# TODO 不從中間打斷順子
# ===============================
# todo 隨機若干局面進行搜索
import random
import json
# Heart Diamond Spade Club
# 3 4 5 6 7 8 9 10 J Q K A 2 joker & Joker
# (0-h3 1-d3 2-s3 3-c3) (4-h4 5-d4 6-s4 7-c4) …… 52-joker->16 53-Joker->17
full_input = json.loads(input())
my_history = full_input["responses"]
use_info = full_input["requests"][0]
poker, history, public_card = use_info["own"], use_info["history"], use_info["public_card"]
last_history = full_input["requests"][-1]["history"]
currBotID = 0 # 判断自己是什么身份,地主0 or 农民甲1 or 农民乙2
if len(history[0]) == 0:
if len(history[1]) != 0:
currBotID = 1
else:
currBotID = 2
history = history[2 - currBotID:]
for i in range(len(my_history)):
history += [my_history[i]]
history += full_input["requests"][i + 1]["history"]
lenHistory = len(history)
for tmp in my_history:
for j in tmp:
poker.remove(j)
poker.sort() # 用0-53编号的牌
def ordinal_transfer(poker):
newPoker = [int(i / 4) + 3 for i in poker if i <= 52]
if 53 in poker:
newPoker += [17]
return newPoker
def transferOrdinal(subPoker, newPoker, poker):
singlePoker, res = list(set(subPoker)), []
singlePoker.sort()
for i in range(len(singlePoker)):
tmp = singlePoker[i]
idx = newPoker.index(tmp)
num = subPoker.count(tmp)
res += [poker[idx + i] for i in range(num)]
return res
def separate(poker): # 拆分手牌牌型并组成基本牌集合
res = []
if len(poker) == 0:
return res
myPoker = [i for i in poker]
newPoker = ordinal_transfer(myPoker)
if 16 in newPoker and 17 in newPoker: # 单独列出火箭/小王、大王
newPoker = newPoker[:-2]
myPoker = myPoker[:-2]
res += [[16, 17]]
elif 16 in newPoker:
newPoker = newPoker[:-1]
myPoker = myPoker[:-1]
res += [[16]]
elif 17 in newPoker:
newPoker = newPoker[:-1]
myPoker = myPoker[:-1]
res += [[17]]
singlePoker = list(set(newPoker)) # 都有哪些牌
singlePoker.sort()
for i in singlePoker: # 分出炸弹,其实也可以不分,优化点之一
if newPoker.count(i) == 4:
idx = newPoker.index(i)
val = myPoker[idx]
res += [myPoker[idx:idx + 4]]
newPoker = newPoker[0:idx] + newPoker[idx + 4:]
myPoker = myPoker[0:idx] + myPoker[idx + 4:]
# 为了简便处理带2的情形,先把2单独提出来
specialCount, specialRes = 0, []
if 15 in newPoker:
specialCount = newPoker.count(15)
idx = newPoker.index(15)
specialRes = [15 for i in range(specialCount)]
newPoker = newPoker[:-specialCount]
myPoker = myPoker[:-specialCount]
def findSeq(p, dupTime, minLen): # 这里的p是点数,找最长的顺子,返回值为牌型组合
resSeq, tmpSeq = [], []
singleP = list(set(p))
singleP.sort()
for curr in singleP:
if p.count(curr) >= dupTime:
if len(tmpSeq) == 0:
tmpSeq = [curr]
continue
elif curr == (tmpSeq[-1] + 1):
tmpSeq += [curr]
continue
if len(tmpSeq) >= minLen:
tmpSeq = [i for i in tmpSeq for _ in range(dupTime)]
resSeq += [tmpSeq]
tmpSeq = []
if len(tmpSeq) >= minLen:
tmpSeq = [i for i in tmpSeq for _ in range(dupTime)]
resSeq += [tmpSeq]
return resSeq
def subSeq(allp, p, subp): # 一定保证subp是p的子集
singleP = list(set(subp))
singleP.sort()
for curr in singleP:
idx = p.index(curr)
countP = subp.count(curr)
p = p[0:idx] + p[idx + countP:]
allp = allp[0:idx] + allp[idx + countP:]
return allp, p
# 单顺:1,5;双顺:2,3;飞机:3,2;航天飞机:4,2。因为前面已经把炸弹全都提取出来,所以这里就不主动出航天飞机了
para = [[1, 5], [2, 3], [3, 2]]
validChoice = [0, 1, 2]
allSeq = [[], [], []] # 分别表示单顺、双顺、三顺(飞机不带翼)
restRes = []
while (True): # myPoker,这里会找完所有的最长顺子
if len(newPoker) == 0 or len(validChoice) == 0:
break
dupTime = random.choice(validChoice)
tmp = para[dupTime]
newSeq = findSeq(newPoker, tmp[0], tmp[1])
for tmpSeq in newSeq:
myPoker, newPoker = subSeq(myPoker, newPoker, tmpSeq)
if len(newSeq) == 0:
validChoice.remove(dupTime)
else:
allSeq[dupTime] += [tmpSeq]
res += allSeq[0] + allSeq[1] # 对于单顺和双顺没必要去改变
plane = allSeq[2]
allRetail = [[], [], []] # 分别表示单张,对子,三张
singlePoker = list(set(newPoker)) # 更新目前为止剩下的牌,newPoker和myPoker是一一对应的
singlePoker.sort()
for curr in singlePoker:
countP = newPoker.count(curr)
allRetail[countP - 1] += [[curr for i in range(countP)]]
# 接下来整合有需要的飞机or三张 <-> 单张、对子。这时候的飞机和三张一定不会和单张、对子有重复。
# 如果和单张有重复,即为炸弹,而这一步已经在前面检测炸弹时被检测出
# 如果和对子有重复,则同一点数的牌有5张,超出了4张
# 先整合飞机
for curr in plane:
lenKind = int(len(curr) / 3)
tmp = curr
for t in range(2): # 分别试探单张和对子的个数是否足够
tmpP = allRetail[t]
if len(tmpP) >= lenKind:
tmp += [i[j] for i in tmpP[0:lenKind] for j in range(t + 1)]
allRetail[t] = allRetail[t][lenKind:]
break
res += [tmp]
if specialCount == 3:
allRetail[2] += [specialRes]
elif specialCount > 0 and specialCount <= 2:
allRetail[specialCount - 1] += [specialRes]
# 之后整合三张
for curr in allRetail[2]: # curr = [1,1,1]
tmp = curr
for t in range(2):
tmpP = allRetail[t]
if len(tmpP) >= 1:
tmp += tmpP[0]
allRetail[t] = allRetail[t][1:]
break
res += [tmp]
res += allRetail[0] + allRetail[1]
return res
def checkPokerType(poker): # poker:list,表示一个人出牌的牌型
poker.sort()
lenPoker = len(poker)
newPoker = ordinal_transfer(poker)
# J,Q,K,A,2-11,12,13,14,15
# 单张:1 一对:2 三带:零3、一4、二5 单顺:>=5 双顺:>=6
# 四带二:6、8 飞机:>=6
typeP, mP, sP = "空", newPoker, []
for tmp in range(2):
if tmp == 1:
return "错误", poker, [] # 没有判断出任何牌型,出错
if lenPoker == 0: # 没有牌,也即pass
break
if poker == [52, 53]:
typeP = "火箭"
break
if lenPoker == 4 and newPoker.count(newPoker[0]) == 4:
typeP = "炸弹"
break
if lenPoker == 1:
typeP = "单张"
break
if lenPoker == 2:
if newPoker.count(newPoker[0]) == 2:
typeP = "一对"
break
continue
firstPoker = newPoker[0]
# 判断是否是单顺
if lenPoker >= 5 and 15 not in newPoker:
singleSeq = [firstPoker + i for i in range(lenPoker)]
if newPoker == singleSeq:
typeP = "单顺"
break
# 判断是否是双顺
if lenPoker >= 6 and lenPoker % 2 == 0 and 15 not in newPoker:
pairSeq = [firstPoker + i for i in range(int(lenPoker / 2))]
pairSeq = [j for j in pairSeq for i in range(2)]
if newPoker == pairSeq:
typeP = "双顺"
break
thirdPoker = newPoker[2]
# 判断是否是三带
if lenPoker <= 5 and newPoker.count(thirdPoker) == 3:
mP, sP = [thirdPoker for k in range(3)], [k for k in newPoker if k != thirdPoker]
if lenPoker == 3:
typeP = "三带零"
break
if lenPoker == 4:
typeP = "三带一"
break
if lenPoker == 5:
typeP = "三带二"
if sP[0] == sP[1]:
break
continue
if lenPoker < 6:
continue
fifthPoker = newPoker[4]
# 判断是否是四带二
if lenPoker == 6 and newPoker.count(thirdPoker) == 4:
typeP, mP = "四带两只", [thirdPoker for k in range(4)]
sP = [k for k in newPoker if k != thirdPoker]
if sP[0] != sP[1]:
break
continue
if lenPoker == 8:
typeP = "四带两对"
mP, sP = [], []
if newPoker.count(thirdPoker) == 4:
mP, sP = [thirdPoker for k in range(4)], [k for k in newPoker if k != thirdPoker]
elif newPoker.count(fifthPoker) == 4:
mP, sP = [fifthPoker for k in range(4)], [k for k in newPoker if k != fifthPoker]
if len(sP) == 4:
if sP[0] == sP[1] and sP[2] == sP[3] and sP[0] != sP[2]:
break
# 判断是否是飞机or航天飞机
singlePoker = list(set(newPoker)) # 表示newPoker中有哪些牌种
singlePoker.sort()
mP, sP = newPoker, []
dupTime = [newPoker.count(i) for i in singlePoker] # 表示newPoker中每种牌各有几张
singleDupTime = list(set(dupTime)) # 表示以上牌数的种类
singleDupTime.sort()
if len(singleDupTime) == 1 and 15 not in singlePoker: # 不带翼
lenSinglePoker, firstSP = len(singlePoker), singlePoker[0]
tmpSinglePoker = [firstSP + i for i in range(lenSinglePoker)]
if singlePoker == tmpSinglePoker:
if singleDupTime == [3]: # 飞机不带翼
typeP = "飞机不带翼"
break
if singleDupTime == [4]: # 航天飞机不带翼
typeP = "航天飞机不带翼"
break
def takeApartPoker(singleP, newP):
m = [i for i in singleP if newP.count(i) >= 3]
s = [i for i in singleP if newP.count(i) < 3]
return m, s
m, s = [], []
if len(singleDupTime) == 2 and singleDupTime[0] < 3 and singleDupTime[1] >= 3:
c1, c2 = dupTime.count(singleDupTime[0]), dupTime.count(singleDupTime[1])
if c1 != c2 and not (c1 == 4 and c2 == 2): # 带牌的种类数不匹配
continue
m, s = takeApartPoker(singlePoker, newPoker) # 都是有序的
if 15 in m:
continue
lenm, firstSP = len(m), m[0]
tmpm = [firstSP + i for i in range(lenm)]
if m == tmpm: # [j for j in pairSeq for i in range(2)]
m = [j for j in m for i in range(singleDupTime[1])]
s = [j for j in s for i in range(singleDupTime[0])]
if singleDupTime[1] == 3:
if singleDupTime[0] == 1:
typeP = "飞机带小翼"
mP, sP = m, s
break
if singleDupTime[0] == 2:
typeP = "飞机带大翼"
mP, sP = m, s
break
elif singleDupTime[1] == 4:
if singleDupTime[0] == 1:
typeP = "航天飞机带小翼"
mP, sP = m, s
break
if singleDupTime[0] == 2:
typeP = "航天飞机带大翼"
mP, sP = m, s
break
omP, osP = [], []
for i in poker:
tmp = int(i / 4) + 3
if i == 53:
tmp = 17
if tmp in mP:
omP += [i]
elif tmp in sP:
osP += [i]
else:
return "错误", poker, []
return typeP, omP, osP
def recover(h): # 只考虑倒数3个,返回最后一个有效牌型及主从牌,且返回之前有几个人选择了pass;id是为了防止某一出牌人在某一牌局后又pass,然后造成连续pass
typeP, mP, sP, countPass = "空", [], [], 0
for i in range(-1, -3, -1):
lastPoker = h[i]
typeP, mP, sP = checkPokerType(lastPoker)
if typeP == "空":
countPass += 1
continue
break
return typeP, mP, sP, countPass
def searchCard(poker, objType, objMP, objSP): # 搜索自己有没有大过这些牌的牌
if objType == "火箭": # 火箭是最大的牌
return []
# poker.sort() # 要求poker是有序的,使得newPoker一般也是有序的
newPoker = ordinal_transfer(poker)
singlePoker = list(set(newPoker)) # 都有哪些牌
singlePoker.sort()
countPoker = [newPoker.count(i) for i in singlePoker] # 这些牌都有几张
res = []
idx = [[i for i in range(len(countPoker)) if countPoker[i] == k] for k in range(5)] # 分别有1,2,3,4的牌在singlePoker中的下标
quadPoker = [singlePoker[i] for i in idx[4]]
flag = 0
if len(poker) >= 2:
if poker[-2] == 52 and poker[-1] == 53:
flag = 1
if objType == "炸弹":
for curr in quadPoker:
if curr > newObjMP[0]:
res += [[(curr - 3) * 4 + j for j in range(4)]]
if flag:
res += [[52, 53]]
return res
newObjMP, lenObjMP = ordinal_transfer(objMP), len(objMP)
singleObjMP = list(set(newObjMP)) # singleObjMP为超过一张的牌的点数
singleObjMP.sort()
countObjMP, maxObjMP = newObjMP.count(singleObjMP[0]), singleObjMP[-1]
# countObjMP虽取首元素在ObjMP中的个数,但所有牌count应相同;countObjMP * len(singleObjMP) == lenObjMP
newObjSP, lenObjSP = ordinal_transfer(objSP), len(objSP) # 只算点数的对方拥有的主牌; 对方拥有的主牌数
singleObjSP = list(set(newObjSP))
singleObjSP.sort()
countObjSP = 0
if len(objSP) > 0: # 有可能没有从牌,从牌的可能性为单张或双张
countObjSP = newObjSP.count(singleObjSP[0])
tmpMP, tmpSP = [], []
for j in range(1, 16 - maxObjMP):
tmpMP, tmpSP = [i + j for i in singleObjMP], []
if all([newPoker.count(i) >= countObjMP for i in tmpMP]): # 找到一个匹配的更大解
if j == (15 - maxObjMP) and countObjMP != lenObjMP: # 与顺子有关,则解中不能出现2(15)
break
if lenObjSP != 0:
tmpSP = list(set(singlePoker) - set(tmpMP))
tmpSP.sort()
tmpSP = [i for i in tmpSP if newPoker.count(i) >= countObjSP] # 作为从牌有很多组合方式,是优化点
species = int(lenObjSP / countObjSP)
if len(tmpSP) < species: # 剩余符合从牌特征的牌种数少于目标要求的牌种数,比如334455->lenObjSP=6,countObjSP=2,tmpSP = [8,9]
continue
tmp = [i for i in tmpSP if newPoker.count(i) == countObjSP]
if len(tmp) >= species: # 剩余符合从牌特征的牌种数少于目标要求的牌种数,比如334455->lenObjSP=6,countObjSP=2,tmpSP = [8,9]
tmpSP = tmp
tmpSP = tmpSP[0:species]
tmpRes = []
idxMP = [newPoker.index(i) for i in tmpMP]
idxMP = [i + j for i in idxMP for j in range(countObjMP)]
idxSP = [newPoker.index(i) for i in tmpSP]
idxSP = [i + j for i in idxSP for j in range(countObjSP)]
idxAll = idxMP + idxSP
tmpRes = [poker[i] for i in idxAll]
res += [tmpRes]
if objType == "单张": # 以上情况少了上家出2,本家可出大小王的情况
if 52 in poker and objMP[0] < 52:
res += [[52]]
if 53 in poker:
res += [[53]]
for curr in quadPoker: # 把所有炸弹先放进返回解
res += [[(curr - 3) * 4 + j for j in range(4)]]
if flag:
res += [[52, 53]]
return res
lastTypeP, lastMP, lastSP, countPass = recover(last_history)
def random_out(poker):
sepRes, res = separate(poker), []
lenRes = len(sepRes)
idx = random.randint(0, lenRes - 1)
tmp = sepRes[idx] # 只包含点数
newPoker, singleTmp = ordinal_transfer(poker), list(set(tmp))
singleTmp.sort()
for curr in singleTmp:
tmpCount = tmp.count(curr)
idx = newPoker.index(curr)
res += [poker[idx + j] for j in range(tmpCount)]
# tmpCount = newPoker.count(newPoker[0])
# res = [[poker[i] for i in range(tmpCount)]]
return res
if countPass == 2: # 长度为0,自己是地主,随便出;在此之前已有两个pass,上一个轮是自己占大头,不能pass,否则出错失败
# TODO 有单张先出单张
res = random_out(poker)
print(json.dumps({
"response": res
}))
exit()
if currBotID == 1 and countPass == 1: # 上一轮是农民乙出且地主选择pass,为了不压过队友选择pass
print(json.dumps({
"response": []
}))
exit()
res = searchCard(poker, lastTypeP, lastMP, lastSP)
lenRes = len(res)
if lenRes == 0: # 应当输出pass
print(json.dumps({
"response": []
}))
else:
pokerOut, typeP = [], "空"
for i in range(lenRes):
pokerOut = res[i]
typeP, _, _ = checkPokerType(pokerOut)
if typeP != "火箭" and typeP != "炸弹":
break
if (currBotID == 2 and countPass == 0) or (currBotID == 1 and countPass == 1):
# 两个农民不能起内讧,起码不能互相炸
# TODO even better
if typeP == "火箭" or typeP == "炸弹":
pokerOut = []
else: # 其他情况是一定要怼的
# TODO better choosing
pokerOut = res[0]
print(json.dumps({
"response": pokerOut
}))
@AcrossTheSky
Copy link

农民甲与农民乙的估价不同

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment