Created
May 19, 2018 08:26
-
-
Save KireinaHoro/bb028d06031ba4a3de2eff3b8aed48b1 to your computer and use it in GitHub Desktop.
FightTheLandlord
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 | |
})) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
农民甲与农民乙的估价不同