python棋牌游戏开发之斗地主

斗地主这个游戏的流程主要有,洗牌,发牌,出牌,而最重要的便是出牌了,出牌里包含着游戏的牌型,所以这里就先给大家讲讲我进行牌型判断的思路。

"""
牌型定义: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)

由于游戏中的通信是阻塞模式,所以客户端我也只用单线程去处理就可以了,用这套代码应该能跑完一次完整的游戏,当然具体是否能跑完我还没测试完过,还有代码要是含有什么错误或者缺漏希望你们能留言告诉我哦,交流交流,共同进步。
下面放组效果图:
在这里插入图片描述
python棋牌游戏开发之斗地主_第1张图片
python棋牌游戏开发之斗地主_第2张图片j叫地主流程

你可能感兴趣的:(python棋牌游戏开发之斗地主)