• 基于STM32设计的UNO卡牌游戏(双人、多人对战)


    一、前言

    UNO扑克是一种起源于欧洲流行于全世界的牌类游戏。简单易学,不用动什么脑筋,适合各年龄层人士玩。

    "UNO"扑克是世界上最大的玩具公司美国美泰玩具公司的代表作,在全球的销售量已超过十亿付!UNO是历史上最成功的玩具,风靡世界!在国外,酒吧和咖啡馆里,UNO是年轻人最喜欢的普及型游戏。

    UNO纸牌已经风靡全球数十年,被誉为是世界上最好玩的纸牌游戏,据说由意大利一个理发师发明,其中UNO的版本众多,被加入许多新的功能,玩法更加刺激,而在此游戏中最考的是集中和反应,还有相互间的思维较量。

    卡牌介绍
    每副UNO牌包括:108张牌(76张数字牌,24张功能牌,8张万能牌)。其中,数字牌与功能牌有红、黄、蓝、绿4种颜色,万能牌没有颜色。

    数字牌
    数字牌共有10种(0、1、2、3、4、5、6、7、8、9),每种4个颜色(红、蓝、黄、绿),每种颜色的0各1张、1~9各2张,共计76张。计分时按数字大小计分。

    功能牌
    普通功能牌同样有四种颜色(红、蓝、黄、绿),共24张,分为禁止,反转,+2 三种,各8张。

    游戏基础规则
    1、决定庄家。庄家可以玩家自己决定。
    2、每人发7张手牌。
    3、翻开牌库顶第一张作为引导牌,庄家根据引导牌出牌,然后玩家打出的牌依次成为引导牌,即最后一张打出的牌是引导牌。
    4、从庄家开始,大家轮流出牌,直至有一个人出完手上的牌。

    游戏行为

    出牌
    出牌时,可以打出与引导牌同样颜色或图案(数字、功能)的牌,也可以使用万能牌。每次只能出1张牌。

    抽牌
    当无牌可出时,要从牌堆顶抽取1张牌。若这张牌可以出则可以立即打出,不能出牌则跳过。

    宣言UNO
    打出倒数第2张手牌时,需要宣言"UNO"(优诺),表示只剩1张手牌,这有些类似于斗地主的报单,但是在UNO中是必须做的。

    事实上,以上只是UNO的其中一些规则,玩家们可以按照自己喜欢的玩法来游戏。

    UNO双人玩法总结:

    (1)牌堆里只有数字牌,数字牌共有10种(0、1、2、3、4、5、6、7、8、9),每种4个颜色(红、蓝、黄、绿),每种颜色的0各1张、1~9各2张,共计76张。计分时按数字大小计分。

    (2)一局游戏限时3分钟,3分钟时间到达之后,谁剩下的牌少就为赢家

    二、设计要求

    项目名称:基于stm32的UNO游戏机设计

    (1)研究如何使用若干个STM32开发板、配套的液晶屏以及无线通信设备实现一个UNO对弈游戏机;

    (2)能够使用无线通信技术,实现短距离内的组网,实现多个STM32的联机功能;

    (3)能够根据UNO游戏规则,为多位玩家提供对弈游戏服务;

    (4)具有一定的动画,提高游戏体验。

    设计要求总结:

    (1)LCD屏上做3个界面:开始界面,游戏界面,结束界面。

    (2)游戏设备做两台,采用ESP8266-WIFI模块实现通信,实现联机对弈。牌先打完的就赢了。根据游戏规则去玩。

    设备A当做主机,使用ESP8266创建热点,并创建游戏房间。

    设备B当做从机,使用ESP8266链接设备A的热点,加入游戏房间。

    设备A与设备B都连接上之后,开始发牌,游戏开始。

    image-20220417165937318

    image-20220417165952476

    image-20220417170010804

    image-20220417170027692

    image-20220417170046517

    image-20220428101019570

    image-20220428101039376

    image-20220428101054617

    image-20220428101115402

    image-20220428101136345

    image-20220428101150996

    image-20220428101210502

    image-20220428101224601

    三、硬件选型

    3.1 STM32开发板

    主控CPU采用STM32F103RCT6,这颗芯片包括48 KB SRAM、256 KB Flash、2个基本定时器、4个通用
    定时器、2个高级定时器、51个通用IO口、5个串口、2个DMA控制器、3个SPI、2个I2C、1个USB、1个
    CAN、3个12位ADC、1个12位DAC、1个SDIO接口,芯片属于大容量类型,配置较高,整体符合硬件选
    型设计。当前选择的这款开发板自带了一个1.4寸的TFT-LCD彩屏,可以显示当前传感器数据以及一些运
    行状态信息。

    image-20220414092118753

    3.2 LCD屏-1.44寸

    image-20220414092149899

    3.3 ESP8266 WIFI

    (1)模块采用串口(LVTTL)与MCU(或其他串口设备)通信,内置TCP/IP协议栈,能够实现串口与WIFI之
    间的转换
    (2)模块支持LVTTL串口,兼容3.3V和5V单片机系统
    (3)模块支持串口转WIFI STA、串口转AP和WIFI STA+WIFIAP的模式,从而快速构建串口-WIFI数据传
    输方案

    image-20220414092301109

    3.4 电容矩阵键盘

    image-20220414092410468

    image-20220414092445597

    四、实现思路

    4.1 流程思路分析

    如果需要源码,可以在这里下载:
    https://download.csdn.net/download/xiaolong1126626497/85896198

    这里有视频演示:

    基于STM32设计的卡牌游戏uno双人对战

    代码分为了两个工程,分别是从机和主机。

    image-20220428101442275

    主机的初始化里,先完成ESP8266的初始化,配置ESP8266为AP+TCP服务器模式,创建一个热点,然后开启TCP服务器,等待从机加入。这里的从机就是玩家,玩家加入房间之后才可以进行游戏过程。

    从机的初始化里,先完成ESP8266的初始化,配ESP8266为STA+TCP客户端模式,去连接指定的热点,也就是主机创建的热点,连接上之后就接着去连接服务器,连接成功,主机就进入下一步,界面上提示请按下#号按键开始游戏。这时候从机也是处于等待状态,当主机按下#号按键之后,主机就会开始洗牌Uno_shuffle(),洗牌两次之后再开始发牌 Uno_DealCard(User,num),发牌完成后会在串口上打印详细的信息,发来信息,然后从牌堆里取出引导牌,接在在主机的LCD界面上完成引导牌显示、对家牌数量,定时器时间、用户ID等信息,给对家发送的牌信息都会存放在static struct clinet_InitCard clinet_init_info结构体里。下面就调用ESP8266_ServerSendData函数将这个clinet_init_info结构体发送出去;当从机收到clinet_init_info结构体之后,解析结构体数据,完成LCD屏界面绘制显示,从机数据解析处理完毕会向主机发送USER_OK指令,表示从机已经准备好可以开始游戏,主机收到USER_OK指令之后,向串口打印游戏正式开始,接着主机界面上就会提示请出牌,然后按下矩阵键盘上的按键选择牌开始出牌。牌的选择是依靠矩阵键盘上的一排排数字,当做牌的下标键,选中拍界面上也会绘制出这张牌的信息,牌选中之后,按下#号按键即可出牌。出牌后,会发送牌的信息给从机完成交互,发送函数:ESP8266_ServerSendData(0,(u8 *)&clinet_info,sizeof(clinet_info))。开始游戏过程中,从机主机之间交互的数据传输就一直使用clinet_info这个结构体变量来存放,实现双方的数据交互通信。从机收到主机发来的clinet_info信息之后,就可以选牌,界面上会显示对家出的牌,也会显示对家牌剩余数量,在串口上也会提示当前可以出的牌是哪些牌,可以在串口调试助手上查看打印的信息。从机选择牌之后,也是和主机一样按下#号按键出牌,然后再发送clinet_info结构体给主机,这样,游戏就开始正确运行了。

    在主机上还有时间显示,3分钟时间到达,就算牌没有出完,游戏也会自动结束,在界面上会提示游戏结束,显示玩家的ID,谁赢了输了。

    功能牌:

    /*
    P -- 禁止牌
    R --反转
    L --加2
    N --普通牌
    U --万能牌
    */
    const char card_func[]={'P','R','L','N','U'};//牌功能信息
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    牌堆初始化:

    void Uno_Init(struct player *pls,u8 cnt)
    {
      u8 i;
      //初始化数字牌:yellow
      for(i=0;i<19;i++)
      {
        cards[i].clr=yellow;//颜色
        cards[i].function=card_none;//功能
        cards[i].num=(i==0)?0:(i%10+i/10);//数字
      }
      //初始化数字牌:red
      for(;i<19*2;i++)
      {
        cards[i].clr=red;//颜色
        cards[i].function=card_none;//功能
        cards[i].num=(i==19)?0:((i-19)%10+(i-19)/10);//数字
      }
      //初始化数字牌:red
      for(;i<19*3;i++)
      {
        cards[i].clr=green;//颜色
        cards[i].function=card_none;//功能
        cards[i].num=(i==19*2)?0:((i-19*2)%10+(i-19*2)/10);//数字
      }  
      //初始化数字牌:blue
      for(;i<19*4;i++)
      {
        cards[i].clr=blue;//颜色
        cards[i].function=card_none;//功能
        cards[i].num=(i==19*3)?0:((i-19*3)%10+(i-19*3)/10);//数字
      }  
      //+2
      for(i=76;i<84;i++)
      {
        cards[i].clr = setClr(i%4); 
        cards[i].function=card_plus2;//加2
        cards[i].num=-1;//-1表示功能牌
      }
      //反转
      for(i=84;i<92;i++)
      {
        cards[i].clr = setClr(i%4); 
        cards[i].function=card_reverse;//反转
        cards[i].num=-1;//-1表示功能牌
      }  
      //禁止
      for(i=92;i<100;i++)
      {
        cards[i].clr = setClr(i%4); 
        cards[i].function=card_prohibit;//禁止
        cards[i].num=-1;//-1表示功能牌
      }
      //万能牌
      for(i=100;i<108;i++)
      {
        cards[i].clr =noColor; 
        cards[i].function=card_universal;//万能
        cards[i].num=-2;//-2表示万能牌
      }  
      for(i=0;i<cnt;i++)
      {
        pls[i].num=0;//玩家卡牌张数清0
      }
      pd.num=0;//牌堆的牌清0
      used.num=0;//已出牌数清0
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    牌堆洗牌:

    void Uno_shuffle(void)
    {
      u16 ran1,ran2,i;
      struct card temp;
      for(i=0;i<UNO_NUMBER*2;i++)//洗两次
      {
        ran1=rand()%UNO_NUMBER;
        ran2=rand()%UNO_NUMBER;
        temp=cards[ran1];
        cards[ran1]=cards[ran2];
        cards[ran2]=temp;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    发牌:

    发牌:每人7张牌
    pls --保存玩家卡牌信息
    cnt --玩家个数
    */
    void Uno_DealCard(struct player *pls,u8 cnt)
    {
      u8 i,j;
      for(i=0;i<cnt;i++)//
      {
        for(j=0;j<7;j++)//每个玩家7张牌
        {
            pls[i].cds[j]=cards[7*i+j];
        }
        pls[i].num=7;
      }
      for(i=0;i<UNO_NUMBER-7*cnt;i++)//剩下的牌信息
      {
        pd.cds[i]=cards[7*cnt+i];
      }
      pd.num=i;//剩下的牌张数
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    摸牌和出牌:

    /***********************从牌堆中摸牌*********************
    **函数功能:从牌堆中取牌
    **pls --保存玩家牌信息
    **cnt --要取的牌张数
    **id  --玩家id
    **返回值:0xff  --摸牌失败
    **         0    --正常摸牌,当前牌不可出
    **        其它值  --当前摸的牌可出
    **********************************************************/
    u8 Uno_takeCard(struct player *pls,u8 cnt,u8 id)
    {
      u8 i=0; 
      if(pd.num<cnt)return 0xff;//剩余牌不足
      struct card temp; 
      for(i=0;i<cnt;i++)
      {
        pls[id].cds[pls[id].num]=pd.cds[pd.num-1];//从剩余牌堆中取牌
        temp=pd.cds[pd.num-1];//保存当前牌信息
        pls[id].num++;//牌张数+1
        pd.num--;//剩余牌-1
      }
      if(cnt==1)//当摸一张牌时,判断当前牌是否可出
      {
        u8 cnt=pls[id].num;
        if(Uno_JudgeCard(&temp)==0)
        {
          return cnt-1;//若当前牌可出,则返回当前牌下标
        }
      }
      return 0;
    }
    /*********************出牌**************************
    **函数功能:玩家出牌
    **pls --保存玩家牌信息
    **index --要出的牌的下标
    **id  --玩家id
    **返回值:0 -- 出牌成功
    **        1 --出牌失败
    **      
    ******************************************************/
    u8 Uno_PlayCard(struct player *pls,u8 index,u8 id)
    {
      if(index>=pls[id].num)return 1;//超出手牌范围
      /*判断是否能出*/
      if(Uno_JudgeCard(&(pls[id].cds[index])))return 1;//所选牌不能出
      /*判断是否为功能牌*/
      struct card temp;
      temp=pls[id].cds[index];
      if(temp.function != card_universal ) //当出的牌不为万能牌时
      {
         guidecare_info=temp;  /*保存当前出的牌,作为下次出牌的引导牌*/
      }
      /*将要出的牌放到已出牌堆中*/
      used.cds[used.num]=temp;
      used.num++;//已出牌数量+1
      /*将后面牌网前移动*/
      u8 i=0;
      for(i=index;i<pls[id].num-1;i++)
      {
        pls[id].cds[i]=pls[id].cds[i+1];
      }
      pls[id].num--;//玩家牌数量-1
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    4.2 使用的相关数据结构

    #ifndef _UNO_H
    #define _UNO_H
    #include "stm32f10x.h"
    #include 
    #include 
    #include "oled.h"
    #include "key.h"
    #include 
    #include "delay.h"
    #include "esp8266.h"
    extern unsigned char HZ_FONT_16[][32];
    extern int uno_time;
    #define LCD_W 127
    #define LCD_H 127
    #define UNO_NUMBER 108 //牌张数
    #define PP 4 //最高玩家数
    /*牌颜色*/
    enum color
    {
      yellow=0,
      red,
      green,
      blue,
      noColor,//没有颜色
      err//出错
    }; 
    /*卡片功能*/
    enum Type
    {
      card_prohibit=0,//禁止牌
      card_reverse,//反转
      card_plus2,//加2
      card_none,//普通牌
      card_universal//万能牌
    };
    /*每一张卡片结构体信息*/
    struct card
    {
      enum color clr;//颜色
      enum Type function;//卡片功能
      int num;//卡片数字
    };
    /*玩家手头卡片信息*/
    struct player
    {
      int num;//牌数
      struct card cds[50];//手头卡片张数
    };
    struct paidui
    {
      int num;//牌数
    	struct card cds[UNO_NUMBER];
    };
    /*主从机之间交互信息*/
    struct card_clinet
    {
      struct card card_guide;//引导牌信息
      u8 id_num;//玩家剩余手牌张数
      u8 user_id;//玩家ID
      u8 pd_remain;//剩余牌堆数量
    };
    /*初始化客户端结构体信息*/
    struct clinet_InitCard
    {
      struct player user_card;//玩家卡牌信息
      struct paidui pd;//牌堆信息
      u8 user_id;//客户端ID
    };
    
    extern enum color colorNow;//牌颜色
    extern enum Type CardType;//卡片功能
    extern struct card cards[UNO_NUMBER];//每一张卡片结构体信息
    extern struct paidui pd,used; //pd-牌堆,used-已出
    extern struct card guidecare_info;//引导牌信息
    void Uno_Init(struct player *pls,u8 cnt);//初始化牌堆
    void Uno_shuffle(void);//洗牌
    void Uno_DealCard(struct player *pls,u8 cnt);//发牌
    u8 Uno_gameStart(u8 num);//开始游戏
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
  • 相关阅读:
    解决九号老C(C30/C40/C60/C80)电动车坐垫感应失灵的问题
    考研机试题
    Java - 发送 HTTP 请求的及其简单的方法模块 - hutool
    Spring5依赖注入(DI)Set方式注入收录
    在 Ubuntu Server 上配置静态 IP 地址
    C++STL【string】下模拟实现string
    通过代码MyBatis-plus实现对表中createTime和updateTime进行自动更新
    第十四届蓝桥杯省赛C++B组D题【飞机降落】题解(AC)
    java计算机毕业设计vue开发一个简单音乐播放器源码+mysql数据库+系统+lw文档+部署
    硬件成本节省60%,四川华迪基于OceanBase的健康大数据数仓建设实践
  • 原文地址:https://blog.csdn.net/xiaolong1126626497/article/details/126088761