斗地主这个游戏的流程主要有,洗牌,发牌,出牌,而最重要的便是出牌了,出牌里包含着游戏的牌型,所以这里就先给大家讲讲我进行牌型判断的思路。
"""
牌型定义:1.单张
2.对子
3.三不带
4.炸
5.三带一
6.连对
7.顺子
8.飞机不带
9.飞机带单
10.飞机带对子/三带二
11.四带2
12.四带一对
牌型判断前提:1.输入的列表为已经从上到下排列的列表
2.只针对一副牌的情况
3.一组列表可能代表着多种牌型,这里没有进行并行判断。
4.这里的列表用“0-13”分别对应扑克牌的“3-鬼”,这里暂时不判大小不区分大小鬼
"""
class doudizhu:
#单张
def dan(self):
return 1
#对子
def dui(self, l):
if l[0] == l[1]:
return 2
else:
return 99
#三不带
def san(self, l):
if l[0] == l[2]:
return 3
else:
return 99
#炸弹
def zha(self, l):
if l[0] == l[3]:
return 4
else:
return 0
#三带一
def sanyi(self, l):
if l[0] == l[2] or l[1] == l[3]:
return 5
else:
return 0
#三带二(未用,与飞机2合并)
def saner(self, l):
if (l[0] == l[2] and l[3] == l[4]) or (l[2] == l[4] and l[0] == l[1]):
return 6
else:
return 0
#顺子(思路:后一个数要比当前数1,有一个发生错误直接返回0)
def shunzi(self,l):
paixing = 0
for i in range(len(l)-1):
if l[i+1]-l[i] == 1:
paixing = 7
else:
return 0
return paixing
#连对(思路:先将长度对半折,同样要判断后2个数比当前数大1,且后一个数与当前数相等)
def liandui(self,l):
paixing = 0
for i in range(int(len(l)/2)-1):
if l[(i+1)*2]-l[i*2] == 1 and l[i*2] == l[i*2+1]:
paixing = 6
else:
return 0
return paixing
#飞机0(思路:每隔3个数后一个数都要比当前数大1,3个数内,后一个数要相等于当前数,未符合条件直接返回0)
def feiji0(self,l):
paixing = 0
for i in range(len(l)-1):
if (i+1)%3 == 0 and l[i+1] - l[i] == 1:
paixing = 8
elif l[i] == l[i+1]:
paixing = 8
else:
return 0
return paixing
#飞机1(思路:计算3个数相同的组合有几组,再与其他未达到3个相同的总牌数比较,相等则正确)
def feiji1(self, l):
a = 1
m = []
for i in range(len(l)-1):
if l[i] == l[i+1]:
a += 1
else:
a = 1
if a == 3:
m.append(l[i])
a = 1
if len(l) - len(m) == len(m)*3:
return 9
else:
return 0
#飞机2(思路:与飞机1类似,不同的是,此处的对子也需要统计,还需要考虑特殊情况(用炸做对子))
def feiji2(self, l):
a = 1
b = 0
m = []
for i in range(len(l)-1):
if l[i] == l[i+1]:
a += 1
if a == 3:
if l[i] != l[i+2]:
m.append(l[i])
a = 1
elif a == 2:
if l[i] != l[i+2]:
b += 1
a = 1
elif a == 4:
b += 2
else:
a = 1
if b == len(m):
return 10
else:
return 0
#四带2(思路,四带2就3种情况,我就不多说了)
def sidaier1(self,l):
if l[0] == l[3] or l[1] == l[4] or l[2] == l[5]:
return 11
else:
return 0
#四代两对(思路:与四带二一样也就3种情况,就是条件还要多加对子判断)
def sidaier2(self, l):
if l[0] == l[3] and l[4] == l[5] and l[6] == l[7]:
return 12
if l[2] == l[5] and l[0] == l[1] and l[6] == l[7]:
return 12
if l[7] == l[4] and l[0] == l[1] and l[2] == l[3]:
return 12
return 0
#这个函数主要就是,对牌型判断前的筛选工作,比如,一张牌就只能是单张,2张牌要先判断是不是王炸等等
def paixing(self, l):
m = len(l)
paixing = 0
if m == 1:
paixing = self.dan()
elif m == 2:
#判断王炸
if l[0] == 13:
paixing = 4
else:
paixing = self.dui(l)
elif m == 3:
paixing = self.san(l)
elif m == 4:
#优先判断炸
paixing = self.zha(l)
#再三带一
if paixing == 0:
paixing = self.sanyi(l)
elif m >= 5:
# 这里的判断基本是并行的,前提是前面的判断没获取到类型
#大于5张都判断一次顺子
paixing = self.shunzi(l)
if paixing == 0:
#然后就是连对判断
if m%2 == 0:
paixing = self.liandui(l)
if paixing == 0:
#飞机不带
if m%3 == 0:
paixing = self.feiji0(l)
if paixing == 0:
#飞机带一
if m%4 == 0:
paixing = self.feiji1(l)
if paixing == 0:
#飞机带对子
if m % 5 == 0:
paixing = self.feiji2(l)
if paixing == 0:
#四带二
if m == 6:
paixing = self.sidaier1(l)
if paixing == 0:
#四带一对
if m == 8:
paixing = self.sidaier2(l)
if paixing == 0:
paixing = 99
return paixing
def chupai(self):
l = [3,3,3,4,4,4,5,5,5,5]
paixing = self.paixing(l)
if paixing == 1:
print("单张")
elif paixing == 2:
print("对子")
elif paixing == 3:
print("三不带")
elif paixing == 4:
print("炸")
elif paixing == 5:
print("三带一")
elif paixing == 6:
print("连对")
elif paixing == 7:
print("顺子")
elif paixing == 8:
print("飞机不带")
elif paixing == 9:
print("飞机带一")
elif paixing == 10:
if len(l) == 5:
print("三带二")
else:
print("飞机带对子")
elif paixing == 11:
print("四带二")
elif paixing == 12:
print("四带两对")
elif paixing == 99:
print("牌型错误")
else:
pass
if __name__ == "__main__":
dou = doudizhu()
dou.chupai()
思路基本都写在代码的注释里了,我想到的牌型基本都验证完毕了,如果有其他牌型却未出现在代码里的,或者你们有更优的想法或思路,或者是我的代码里会有某些错误,都可以通过博客下面的留言跟我一起探讨哦
7.15更新
上次只是基本判断牌型,这次更新弄了个文字版的斗地主,通信协议用的是socket/tcp, 传送数据格式为JSON,这里将牌型判断统一放到服务器上弄,但是如果是为了高效通信,建议将牌型判断转移到客户端上,我这里就不再转移了。话不多说,上代码。
一、斗地主常用流程模块
1.牌型判断
class doudizhu:
#单张
def dan(self):
return 1
#对子
def dui(self, l, paixing):
if l[0] == l[1]:
paixing = 2
return paixing, l[0]
#三不带
def san(self, l,paixing):
if l[0] == l[2]:
paixing = 3
return paixing, l[0]
#炸弹
def zha(self, l,paixing):
if l[0] == l[3]:
paixing = 4
return paixing, l[0]
#三带一
def sanyi(self, l, paixing):
if l[0] == l[2] or l[1] == l[3]:
paixing = 5
return paixing, l[2]
#三带二(未用,与飞机2合并)
def saner(self, l, paixing):
if (l[0] == l[2] and l[3] == l[4]) or (l[2] == l[4] and l[0] == l[1]):
paixing = 6
return paixing, l[2]
#顺子(思路:后一个数要比当前数大1,有一个发生错误直接返回0)
def shunzi(self, l, paixing):
now = 0
print(l)
if l[-1] < 12:
for i in range(len(l)-1):
if l[i+1]-l[i] == 1:
now = 7
else:
return paixing, l[-1]
paixing = now
return paixing, l[-1]
#连对(思路:先将长度对半折,同样要判断后2个数比当前数大1,且后一个数与当前数相等)
def liandui(self, l, paixing):
now = 0
print(l[-1])
if l[-1] < 12:
for i in range(int(len(l)/2)-1):
if l[(i+1)*2]-l[i*2] == 1 and l[i*2] == l[i*2+1]:
now = 6
else:
return paixing, l[-1]
paixing = now
return paixing, l[-1]
#飞机0(思路:每隔3个数后一个数都要比当前数大1,3个数内,后一个数要相等于当前数,未符合条件直接返回0)
def feiji0(self, l, paixing):
now = 0
for i in range(len(l)-1):
if (i+1)%3 == 0 and l[i+1] - l[i] == 1:
now = 8
elif l[i] == l[i+1]:
now = 8
else:
return paixing, l[-1]
paixing = now
return paixing, l[-1]
#飞机1(思路:计算3个数相同的组合有几组,再与其他未达到3个相同的总牌数比较,相等则正确)
def feiji1(self, l, paixing):
a = 1
m = []
for i in range(len(l)-1):
if l[i] == l[i+1]:
a += 1
else:
a = 1
if a == 3:
m.append(l[i])
a = 1
if len(m)*3 == len(l):#特殊情况(4个3张)
if m[-1] - m[1] == len(m) - 2:
paixing = 9
a = m[-1]
elif m[-2] - m[0] == len(m) - 2:
paixing = 9
a = m[-2]
if len(m) != 0:#排除无3个杂牌,避免后免的m[0],m[-1]出错
if len(l) - len(m) == len(m)*3 and m[-1] - m[0] == len(m) - 1:
paixing = 9
a = m[-1]
return paixing, a
#飞机2(思路:与飞机1类似,不同的是,此处的对子也需要统计,还需要考虑特殊情况(用炸做对子))
def feiji2(self, l, paixing):
a = 1
b = 0
m = []
for i in range(len(l)-1):
if l[i] == l[i+1]:
a += 1
if a == 3:
if i+2 > len(l) or l[i] != l[i+2]:
m.append(l[i])
a = 1
elif a == 2:
if l[i] != l[i+2]:
b += 1
a = 1
elif a == 4:
b += 2
else:
a = 1
if len(m) != 0:#情况同上
if b == len(m) and m[-1] - m[0] == len(m) - 1:
paixing = 10
b = m[-1]
else:
b = l[-1]
return paixing, b
#四带2(思路,四带2就3种情况,我就不多说了)
def sidaier1(self, l, paixing):
if l[0] == l[3] or l[1] == l[4] or l[2] == l[5]:
paixing = 11
return paixing, l[2]
#四代两对(思路:与四带二一样也就3种情况,就是条件还要多加对子判断)
def sidaier2(self, l, paixing):
m = 0
if l[0] == l[3] and l[4] == l[5] and l[6] == l[7]:
paixing = 11
m = l[0]
if l[4] == l[7]:
m = l[4]
elif l[2] == l[5] and l[0] == l[1] and l[6] == l[7]:
paixing = 11
m = l[2]
elif l[7] == l[4] and l[0] == l[1] and l[2] == l[3]:
paixing = 11
m = l[4]
return paixing, m
def first(self, l):
m = len(l)
print("len m =",m)
paixing = 0#牌型的中间变量
n = []#用列表存牌型,用来应对多种牌型情况
bf_max = []#用列表存比对值,用来应对多种牌型情况
y = 0#判断同牌型的中间变量
if m == 2:
#判断王炸
if l[0] == 13:
print("wangzha")
paixing = 4
n.append(paixing)
bf_max.append(13)
else:
paixing, y = self.dui(l, paixing)
if paixing:
n.append(paixing)
paixing = 0
bf_max.append(y)
y = 0
elif m == 3:
paixing, y = self.san(l, paixing)
if paixing:
n.append(paixing)
paixing = 0
bf_max.append(y)
y = 0
elif m == 4:
#优先判断炸
print("zha")
paixing, y = self.zha(l, paixing)
if paixing:
n.append(paixing)
paixing = 0
bf_max.append(y)
y = 0
#再三带一
paixing, y = self.sanyi(l, paixing)
if paixing:
n.append(paixing)
paixing = 0
bf_max.append(y)
y = 0
paixing = 0
elif m >= 5:
#大于5张都判断一次顺子
paixing, y = self.shunzi(l, paixing)
paixing, y = self.saner(l, paixing)
#然后就是连对判断
if m%2 == 0:
paixing, y = self.liandui(l, paixing)
if paixing:
n.append(paixing)
paixing = 0
bf_max.append(y)
y = 0
#飞机不带
if m%3 == 0:
paixing, y = self.feiji0(l, paixing)
if paixing:
n.append(paixing)
paixing = 0
bf_max.append(y)
y = 0
#飞机带一
if m%4 == 0:
paixing, y = self.feiji1(l, paixing)
if paixing:
n.append(paixing)
paixing = 0
bf_max.append(y)
y = 0
#飞机带对子
if m % 5 == 0:
paixing, y = self.feiji2(l, paixing)
if paixing:
n.append(paixing)
paixing = 0
bf_max.append(y)
y = 0
#四带二
if m == 6:
paixing, y = self.sidaier1(l, paixing)
if paixing:
n.append(paixing)
paixing = 0
bf_max.append(y)
y = 0
#四带两对
if m == 8:
paixing, y = self.sidaier2(l, paixing)
if paixing:
n.append(paixing)
paixing = 0
bf_max.append(y)
y = 0
return n, bf_max
这个部分跟上次的对比更新了一些判断方式,不仅使得判断更加准确,而且适配了下面的出牌大小比较,而first()函数更是几乎包括了所有的牌的判断,除了单张类型。
2、出牌比较
def comp(self, bf_type, bf_max, bf_lenth, l, socket):
m = len(l)#当前出牌的长度
x = 0#临时接收客户端信息
n = []#存放经过转化排列的出牌
#转化出牌,使得与之前的设计相符
for i in l:
n.append(int(i/4))
n.sort()#排列,这里有个易错点,sort()函数没有返回值,不需要也不能用其他列表去复制它,直接调用便能将n排列好
now_type = []#当前出牌牌型
now_max = []#当前出牌值
if m == 1:
if l[0] < 52:#小鬼大鬼单独算
now_type.append(1)
now_max.append(n[0])
else:
now_type.append(1)
now_max.append(l[0])
else:
now_type, now_max = self.first(n)
if len(now_type) != 0:#如果长度为0,则代表没有牌型,也就是出牌错误
if bf_type == 0:#未出牌,不需要与上次牌型比较
if len(now_type) > 1:#多种牌型判断
data = {
'type': "select",
'paixing': now_type
}
socket.send(json.dumps(data).encode())#像客户端发起牌型判断请求
x = socket.recv(1024)
if now_type[x] != 0:#0为无牌型,排除错误情况
return True, now_type[x], now_max[x], m
else:
j = 0#计算合理有效牌型数量
r = 0#存放第一个合理牌型
for i in range(len(now_type)):
if now_type[i] == 4 and bf_type != 4:#目前出牌牌型为炸弹,前一刻不为炸弹
j += 1
elif now_type[i] == 4 and bf_type == 4 and now_max[i] > bf_max:
j += 1
elif now_type[i] == bf_type and now_max[i] > bf_max and m == bf_lenth:
j += 1
if j == 1:
r = i
if j > 1:#多种牌型,请求用户抉择
data = {
'type': "select",
'paixing': now_type
}
socket.send(json.dumps(data).encode())
x = socket.recv(1024).decode()
if now_type[x] != 0:
return True, now_type[x], now_max[x], m
elif j == 1:#一种牌型,直接返回
return True, now_type[r], now_max[r], m
return False, bf_type, bf_max, bf_lenth
这里就是斗地主游戏过程中经常要做的事,将上次出牌与下次出牌进行比较。
3、模拟洗牌、发牌
def beginer(self):#洗牌函数
a = [x for x in range(54)]#初始化列表0-53
random.shuffle(a)#随机打乱列表
a1 = a[1::3]#切片取数
a2 = a[2::3]
a3 = a[3::3]
a4 = a[-3:]#底牌
del a1[-1]#删除多余数
del a2[-1]#删除多余数
return a1, a2, a3, a4
这部分就比较简单,用0-53代表扑克牌,用随机打乱列表的函数来模拟洗牌。
4、叫地主
def jdz(self, client, i):#叫地主函数
a = 0#叫地主次数统计
b = 0#不叫次数统计
now = ""#记录当前拥有地主权的玩家
restart = False#重开标志
z = random.randint(0, 2)#随机取数,优先叫地主
n = z#复制此变量
client_ls = client[i]#临时存放当前房间用户的名字
while True:
jdz = "叫地主"
if a != 0:#模拟抢地主流程,有人叫地主之后便是抢地主
jdz = "抢地主"
if a == len(client_ls) and a > 1:#模拟先叫地主的人,有一次我抢的机会
jdz = "我抢"
#json格式传送信息
data = json.dumps({
"type": "jdz",
"message": jdz
})
client[client_ls[n]].send(data.encode())
recv = client[client_ls].recv(1024).decode()#接收抢地主信息
if recv == 0:
print("wait for user %s" % client_ls[n])
else:
recv = json.loads(recv)
if recv["message"] == "yes":#用户选择抢地主
a += 1
now = client[i][n]
else:
b += 1
data = json.dumps({
"type": "warning",
"message": client[i][n]+":"+jdz
})
#给其他用户发送抢地主信息
client[client[i][(n + 1) % 3]].send(data.encode())
client[client[i][(n + 2) % 3]].send(data.encode())
#抢地主全部情况
if a == 0 and b == 3:
restart = True
now = client[i][z]
break
elif a == 1 and b == 2:
break
elif a + b == 4:
break
if a == 0:#假如不抢
del client_ls[n]#去除此人的抢地主机会
if n > len(client_ls):#顺位改变
n = n % len(client_ls)
else:
n = (n+1) % len(client_ls)
return now, restart
这部分函数就是模拟平常欢乐斗地主的叫地主、抢地主流程。
二、服务器模块
1、监听用户连接模块
def connect(s):
while True:
print('server socket waiting...')
obj, addr = s.accept() # 阻塞等待链接
client[obj] = 1#开启用户通信
t1 = threading.Thread(target=user, args=(obj,))#独立线程处理用户通信
t1.start()
简单的阻塞监听,多线程处理,这里可以用线程池代替普通线程去管理资源,我就不多介绍了。
2、用户的普通通信
def user(socket):
while True:
if client[socket]:
data = socket.recv(1024).decode()
if data == 0:#异常断开
break
else:
data = json.loads(data)#json解码
if data["type"] == "login":#登录信息处理
if data["name"] in client:
message = json.dumps({
"type": "warning",
"message": "昵称已存在,请重新输入"
})
socket.send(message.encode())#重复昵称判断
else:
client[data["name"]] = socket#字典存通信地址与用户名
print("玩家加入:%s" % data["name"])#显示玩家加入
elif data["type"] == "ready":#玩家准备才会进入游戏
for i in range(33):#遍历房间
if i == 32 and len(client[i]) == 3:#预设定房间是否满人判断
message = json.dumps({
"type": "warnning",
"message": "房间已满"
})
socket.send(message.encode())
if len(client[i]) < 3:#自动寻找未满房间进入
client[i].append(data["name"])#i房间添加用户
message = json.dumps({
"type": "warning",
"message": "已加入房间%s" % i
})
socket.send(message.encode())
break
client[socket] = 0#关闭正常通信
#退出房间(未使用)
elif data["type"] == "cancel":
i = 0
j = 0
for i in range(33):
for j in range(len(client[i])):
if client[i][j] == data["name"]:
break
message = json.dumps({
"type": "warning",
"message": "已退出房间%s" % i
})
socket.send(message.encode())
client[i][j].delect()
station[i] = 0
#断开链接(未使用)
elif data["type"] == "exit":
break
#继续游戏(未使用)
elif data["type"] == "continue":
client[socket] = 2
socket.close()
#向其他玩家发送出牌的消息
这里就是用来与用户游戏前的通信逻辑。
3、可复用函数
#向其他玩家发送出牌的消息
def message(l, client, now ,i):
for j in range(3):
data = json.dumps({
"type": "chupai1",
"name": now,
"card": l
})
if client[i][j] != now:
client[client[i][j]].send(data.encode())
#更新用户的牌与牌数
def fapai(usercard, client, i):
for j in range(3):
data = json.dumps({
"type": "fapai",
"player1": client[i][(j + 1) % 3] + ":" + str(len(usercard[client[i][(j + 1) % 3]])),
"player2": client[i][(j + 2) % 3] + ":" + str(len(usercard[client[i][(j + 2) % 3]])),
client[i][j]: usercard[client[i][j]]
})
client[client[i][j]].send(data.encode())
#删除已出的牌,以及是否结束游戏判断
def up_card(usercard, now,l):
i = 0
j = 0
for i in l:#遍历出牌的列表
for j in range(len(usercard[now])):
if usercard[now][j] == i:#找到相同的牌
break
del usercard[now][j]#删除此牌
j = 0
fapai(usercard, client, i)#更新用户的牌
for i in usercard:#遍历用户的牌
if len(usercard[i]) == 0:#是否有人已经出完牌了
return True, i
return False, i
#游戏模式
这里主要是将一些重要的逻辑单独隔离出来写。
4、游戏中的通信
#游戏模式
def player(i, client):
restart = True#不抢地主重开
result = True#结果,用来终止循环
count = 0#重开计数,模拟3次重开后不能再重开
buchu = 0#不出计数,模拟其他两个不出后,可以随意出牌型
now = ""#存放当前通信玩家
begin = 0#抢地主流程结束标志
usercard = {}#存放用户的牌
dizhu_name = ""#本次游戏的地主
nongming1 = ""#本次游戏的农民1
nongming2 = ""#本次游戏的农民2
doudizhu = chupai.doudizhu()#创建对象
bf_type = 0#上次出牌类型
bf_max = 0#上次出牌值
bf_lenth = 0#上次出牌长度
a = [[], [], [], []]#初始化a
a[0], a[1], a[2], a[3] = doudizhu.beginer()#用a列表来存放底牌以及各用户的初始牌
for j in range(3):
usercard[client[i][j]] = a[j]#将牌存入usercard中
fapai(usercard, client, i)#给用户发牌
while result:
if restart and count < 3:
if count > 0:#重开
for j in [0, 1, 2, 3]:
a[j].clear()
a[0], a[1], a[2], a[3] = doudizhu.beginer()
for j in range(3):
usercard[client[i][j]] = a[j]
fapai(usercard, client, i)
now, restart = doudizhu.jdz(client, i)#进入叫地主流程
count += 1#计数
else:
if not begin:
usercard[now] += a[3]#给地主加底牌
dizhu_name = now#存地主名字
fapai(usercard, client, i)
for j in [0,1,2]:
if client[i][j] == now:#存农民名字
nongming1 = client[i][int((j+1)/3)]
nongming2 = client[i][int((j + 2) / 3)]
break
begin = 1#进入出牌流程
if begin:
data = json.dumps({
"type": "chupai"
})
client[now].send(data.encode())
recv = json.loads(client[now].recv(1024).decode())
if recv["type"] == "chupai":#出牌标志
l = recv["card"]#存放出牌的列表
res, bf_type, bf_max, bf_lenth = doudizhu.comp(bf_type, bf_max, bf_lenth, l, client[now])#判断出牌是否错误
if res:#正确牌型
buchu = 0
data = json.dumps({
"type": "chupai2"
})#通知出牌用户的打印出牌消息
client[now].send(data.encode())
message(l, client, now, i)#通知其他用户的打印出牌消息
res1, winner = up_card(usercard, now, l)#去牌和胜负判断
for j in [0, 1, 2]:
if res1:#胜负标志
data3 = []
if winner == dizhu_name:#赢家是地主
data3.append(json.dumps({
"type": "win"
}))
data3.append((json.dumps({
"type": "false"
})))
else:#赢家是农民
data3.append(json.dumps({
"type": "false"
}))
data3.append((json.dumps({
"type": "win"
})))
client[dizhu_name].send(data3[0].encode())
client[nongming1].send(data3[1].encode())
client[nongming2].send(data3[1].encode())
result = not res1
break
else:
if client[i][j] == now:
now = client[i][(j + 1) % 3]
break
else:
data = json.dumps({
"type": "error"
})
client[now].send(data.encode())
elif recv["type"] == "buchu":
buchu += 1#不出计数
for j in [0, 1, 2]:
if client[i][j] == now:
now = client[i][(j + 1) % 3]
break
if buchu == 2:#两个不出,第三个人可以随意
bf_type = 0
bf_lenth = 0
bf_max = 0
buchu = 0
这里的通信主要用阻塞式的,单个房间的通信用单个线程去处理,对于斗地主这个游戏来说是够的。如果你要多线程,建议先将通信逻辑与算法逻辑完全隔离封装。
5、启动代码
if __name__ == "__main__":
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)#TCP/IP流
HostPort = ('127.0.0.1', 6666)#ip与端口
s.bind(HostPort) # 绑定地址端口
s.listen(99) # 监听连接数,超过次数会拒绝之后的用户
client = {}#主字典,存放房间信息,登录用的名称与地址
station = [0 for x in range(33)]#存放房间状态,0代表等待,1代表游戏中
for i in range(33):
client[i] = []
t = threading.Thread(target=connect, args=(s,))#线程开启处理连接
t.start()
while True:
for i in range(33):
if station[i] == 1 and client[i][0] == 2 and client[i][1] == 2 and client[i][2] == 2:#暂无用处,预设定
station[i] = 0
if len(client[i]) == 3 and station[i] == 0:#房间状态判断
t2 = threading.Thread(target=player, args=(i, client))#开线程处理游戏
t2.start()
print("房间%s启动" % i)
station[i] = 1
这里包含了房间的预创建,房间状态的管理等,是服务器的启动代码。
三、客户端
#接收信息处理函数
def recv(s, d):
l = []
name = input("请输入用户名:")#输名字,用于存储地址和识别
data2 = json.dumps({
"type": "login",
"name": name
})
s.send(data2.encode())
inp = input("开始匹配:")#预设定,可改为可选房间(目前无用)
data2 = json.dumps({
"type": "ready",
"name": name
})
s.send(data2.encode())
while True:
rec = s.recv(1024).decode()
if rec == 0:
break
else:
rec = json.loads(rec)
if rec["type"] == "fapai":#显示自己的牌与其他玩家的牌数
card = ""
rec[name].sort()#对牌排序,使得显示的时候更加美观
for i in rec[name]:#用空格拼接牌
card = card + d[i] + " "
sys.stdout.write("{}\n{}\n我:{}\n".format(rec["player1"], rec["player2"], card))#显示排序,可用print()代替
sys.stdout.flush()
elif rec["type"] == "warning":#服务器通知用户显示通知
sys.stdout.write("{}\n".format(rec["message"]))#显示通知
sys.stdout.flush()
elif rec["type"] == "chupai":#服务器通知用户出牌
l.clear()#预清空
data = {
"type":"chupai",
"card": l
}
while True:
try:#用try防止使程序结束
inp = input("请出牌:")
if inp == "buchu":
data["type"] = "buchu"
else:
inp = inp.split()#规定每张牌以空格隔开
for i in inp:
l.append(d[i])
data["card"] = l
break
except KeyError:
print("输入错误,请检查")#出错通知
data1 = json.dumps(data)
s.send(data1.encode())
elif rec["type"] == "chupai1":#打印别的用户出的牌,这里可以改的更高大上,显示牌时同时显示其牌型
card = ""
rec["card"].sort()#排序
for i in rec["card"]:
card = d[i] + ""
sys.stdout.write("{}出牌:{}\n".format(rec["name"], card))
sys.stdout.flush()
elif rec["type"] == "chupai2":#显示自己出的牌
card = ""
for i in l:
card = d[i] + ""
sys.stdout.write("我出牌:{}\n".format(card))
sys.stdout.flush()
elif rec["type"] == "win":#显示胜利信息
sys.stdout.write("I'm win")
sys.stdout.flush()
elif rec["type"] == "false":#显示失败信息
sys.stdout.write("You are false")
sys.stdout.flush()
elif rec["type"] == "error":#显示出牌错误信息
l.clear()
sys.stdout.write("牌型错误,请重新出牌")
sys.stdout.flush()
elif rec["type"] == "jdz":#叫地主流程
s1 = "不叫"
if not rec["message"] == "叫地主":
s1 = "不抢"
sys.stdout.write("1.{} 2.{}\n".format(rec["message"], s1))
sys.stdout.flush()
inp = input("请输入:")
data = {
"type": "jdz",
"message": "no"
}
try:#与上面的try用处一致
inp = int(inp)
if inp == 1:
data["message"] = "yes"
except:
pass
data = json.dumps(data)
s.send(data.encode())
elif rec["type"] == "select":#显示多牌型选择
data1 = 0
while True:
try:
inp = int(input("请输入0-%s:%s:" % (len(rec["paixing"]), rec["paixing"])))
data1 = rec["paixing"][inp]
data1 = inp
break
except:
print("输入错误")
s.send(data1)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
HostPort = ('127.0.0.1', 6666)
s.connect(HostPort)#连接
d = show()
recv(s, d)
由于游戏中的通信是阻塞模式,所以客户端我也只用单线程去处理就可以了,用这套代码应该能跑完一次完整的游戏,当然具体是否能跑完我还没测试完过,还有代码要是含有什么错误或者缺漏希望你们能留言告诉我哦,交流交流,共同进步。
下面放组效果图:
在这里插入图片描述