基于STM32的五子棋游戏

前言

之前写过一篇文章是关于C语言实现五子棋,知道了实现五子棋的基本原理。本来打算很快的把五子棋移植到STM32F103精英版上,但是前几周实验室纳新、期中考试、开放实验各种事情导致一直没有重大进展。在忙完以后终于在这个周完成了五子棋的全部功能。在调试的过程中总是会出现各种问题,然后去寻找问题、解决问题。这也是第一次一个人独自做一个比较完备的具体项目,通过项目学到了很多知识以及知道如何具有一个系统的思维来完成这样一个项目。

项目要求

基础要求:实现下棋并判断输赢和基本的游戏介绍
高级要求:界面美观、可以选择人机模式、双人对战模式、智能提示落子点、悔棋以及一些简单设置。

项目总体设计

通过定义一个16*16的数组g_Chess[16][16]和一个记录棋子数量的变量g_ChessNum。初始化棋子总数为0且每点都没有棋子,则初始化数组全部为0。每当检测到有棋子落下时棋子总数加一,而该点的坐标所对应的数组的值为下该棋子时棋子的总数。
模式选择:定义一个变量g_Mode,初始化g_Mode=0。如果g_Mode=1则是人机模式,如果g_Mode=2则是双人对战模式
下棋:判断该点坐标对应的数组的值是否为0,如果是0则可以下棋,如果不是0则返回重新判断。如果是人机模式,则在触摸屏没有按下时进行计算相关棋子坐标。每下一颗棋子后判断是否连成5颗棋子。如果连接成5颗棋子
判断白棋或者黑棋:通过棋子总数对2求余来判断该点是黑棋还是白棋。
悔棋:让该点坐标对应的数组值等于0棋子总数减1,清除该棋子。如果是人机对战,则清理最后下的两颗棋子,棋子总数减二。
重新开始:让数组的值都为0以及棋子总数等于0,重新画界面
返回:返回主界面选择模式。

软件实现

重要绘制图形函数介绍

绘制按钮函数

void DrawButton(int x, int y, int width, int height, u8* Text, char sta)
{
 int iLen = strlen((char*)Text);
 
 if (x > 239 || x < 0 || y < 0 || y > 319) return;
 
 POINT_COLOR = WHITE;
 LCD_DrawRectangle(x, y, x+width, y+height);
 if (sta==1) 
 {
  LCD_Fill(x+1, y+1, x+width-1, y+height-1, BLUE);
  BACK_COLOR = BLUE;
  Show_Str(x+((width-iLen*8)/2), y+((height-16)/2), 200, 16, Text, 16, 0);
 }
 else if(sta==0) 
 {
  LCD_Fill(x+1, y+1, x+width-1, y+height-1, GRAY);
  BACK_COLOR = GRAY;
  Show_Str(x+((width-iLen*8)/2)+2, y+((height-16)/2)+2, 200, 16, Text, 16, 0);
 }
 else if(sta==2)
 {
  LCD_Fill(x+1, y+1, x+width-1, y+height-1, GREEN );
  BACK_COLOR = GREEN ;
  Show_Str(x+((width-iLen*8)/2)+2, y+((height-16)/2)+2, 200, 16, Text, 16, 0);
 } 
}

可以通过调用该函数来实现画按钮,通过改变参数sta的值来改变按钮的背景颜色

sta=1sta=2sta=0
蓝色绿色灰色

绘制棋子函数

void DrawChess(int x, int y, int num, char sta)
{
 uint16_t ChessColor;
 uint16_t xChess;
 uint16_t yChess;
 
 if (x>15 || x<0 || y>15 || y<0)return; //超范围了
 //判断是下棋还是复盘
 if (sta)
 {
  if (g_Chess[x][y]!=0)return;   //已有棋子
 }
 
 //判断棋子颜色
 (num%2==0) ? (ChessColor=BLACK) : (ChessColor=WHITE);
 
 //计算棋子坐标
 xChess = CHESSBOARD_START_X + (x*CHESSGRID_SIZE);
 yChess = CHESSBOARD_START_Y + (y*CHESSGRID_SIZE);
 
 LcdDrawCircleA(xChess, 
       yChess, 
       CHESSGRID_SIZE/2-1, 
       ChessColor, 
       1);
 if(voice==1)          //判断灯光是否打开 若打开 每次下子蜂鸣器响100ms
 {
  LED0=0;
  delay_ms(100);
  LED0=1;
 }
 delay_ms(10);
 if (sta == 0) return;
 g_ChessNum++;
 g_Chess[x][y] = g_ChessNum;
}

每画一个棋子棋子数量加一,在判断画棋子前判断是否可以下棋,同时判断灯光是否打开。如果可以下棋且灯光打开每下一颗棋LE0闪烁100ms。

绘制棋盘界面函数

void DrawChessBoard(void)
{
 int i;
 //填充棋盘背景色
 LCD_Fill(CHESSBOARD_START_X-5, CHESSBOARD_START_Y-5, 
   CHESSBOARD_END_X+5, CHESSBOARD_END_Y+5, CHESSBOARD_COLOR);
 POINT_COLOR = BLACK;
 //绘制边框线
 LCD_DrawRectangle(CHESSBOARD_START_X-3, CHESSBOARD_START_Y-3,
       CHESSBOARD_END_X+3, CHESSBOARD_END_Y+3);
 LCD_DrawRectangle(CHESSBOARD_START_X-4, CHESSBOARD_START_Y-4,
       CHESSBOARD_END_X+4, CHESSBOARD_END_Y+4);
 LCD_DrawRectangle(CHESSBOARD_START_X, CHESSBOARD_START_Y, 
       CHESSBOARD_END_X, CHESSBOARD_END_Y);
 
 POINT_COLOR = BLACK;
 //绘制垂直线
 for (i = 1; i < 15; i++)
 {
  LCD_DrawLine(CHESSBOARD_START_X,
      CHESSBOARD_START_Y + (CHESSGRID_SIZE*i),
      CHESSBOARD_END_X,
      CHESSBOARD_START_Y + (CHESSGRID_SIZE*i));
 }
 
 //绘制水平线
 for (i = 1; i < 15; i++)
 {
  LCD_DrawLine(CHESSBOARD_START_X + (CHESSGRID_SIZE*i),
      CHESSBOARD_START_Y,
      CHESSBOARD_START_X + (CHESSGRID_SIZE*i),
      CHESSBOARD_END_Y);
 }
 DrawButton(5, 285, 80, 25, (u8*)"重新开始", 1);
 DrawButton(87, 285, 48, 25, (u8*)"悔棋", 1);
 DrawButton(137, 285, 48, 25, (u8*)"帮助", 1);
 DrawButton(187, 285, 49, 25, (u8*)"返回", 1);
}

重要界面函数介绍

模式选择界面

void Main(void)//主界面函数
{
 LCD_Clear(GBLUE);
 BACK_COLOR = GBLUE;
 POINT_COLOR = RED;
 Show_Str((240 - 24*3)/2, 30, 200, 24, (u8*)"五子棋", 24, 0); 
 //创建按钮
 DrawButton(40, 100, 160, 40, (u8*)"人机对战", 1);
 DrawButton(40, 150, 160, 40, (u8*)"双人对战", 1);
 DrawButton(40, 200, 160, 40, (u8*)"游戏介绍", 1);
 DrawButton(40, 250, 160, 40, (u8*)"设置", 1);
}

Alt

对战界面

void One(void)
{
 LCD_Clear(BRRED);
 POINT_COLOR = WHITE;
 DrawChessBoard();
 BACK_COLOR = BRRED;
 POINT_COLOR=BLACK;
 Show_Str(34, 10, 240, 34, (u8*)"五子棋人机对战", 24, 0);
 One_test();
}

Alt

设置界面

void Setting(void)
{
 POINT_COLOR = WHITE;
 //绘制关于窗口
 LCD_DrawRectangle(20, 110, 220, 220);
 LCD_DrawRectangle(21, 111, 219, 219);
 LCD_Fill(22, 112, 218, 218, BLACK);
 BACK_COLOR = BLACK;
 Show_Str(104, 115, 32, 16, (u8*)"设置", 16, 0);
 Show_Str(40, 140, 136, 16, (u8*)"灯光", 16, 0);
 Show_Str(40, 165, 136, 16, (u8*)"关于", 16, 0);
 //绘制按钮
 DrawButton(150, 140, 25, 20, (u8*)"开", 2);
 DrawButton(175, 140, 25, 20, (u8*)"关", 0);
 DrawButton(150, 165, 50, 20, (u8*)"打开", 2);
 DrawButton(90, 195, 60, 20, (u8*)"返回", 1);
 Set_test();
} 

在这里插入图片描述

关于界面

void About(void)
{
 POINT_COLOR = WHITE;
 //绘制关于窗口
 LCD_DrawRectangle(20, 110, 220, 220);
 LCD_DrawRectangle(21, 111, 219, 219);
 LCD_Fill(22, 112, 218, 218, BLACK);
 BACK_COLOR = BLACK;
 //文本
 Show_Str(104, 115, 32, 16, (u8*)"关于", 16, 0);
 Show_Str(40, 135, 136, 16, (u8*)"平台:STM32F103精英版", 16, 0);
 Show_Str(40, 155, 136, 16, (u8*)"作者:YWL", 16, 0);
 Show_Str(40, 175, 136, 16, (u8*)"时间:2019-11-30", 16, 0);
 //绘制按钮
 DrawButton(90, 195, 60, 20, (u8*)"返回", 1);
 About_test();
}

在这里插入图片描述

重要屏幕测试相关函数

屏幕测试模块原理基本差别不大,主要是位置的差别。博主在这里主要介绍单人对战时的屏幕测试函数

单人对战下棋函数

void One_test(void)//双人对战屏幕扫描
{
 u16 x, y;
 static int xChess = 0;
 static int yChess = 0;
// static int xPre = 0;
// static int yPre = 0;
 static uint8_t State = 0;
 
 //判断触摸屏是否被按下
 if (tp_dev.sta&TP_PRES_DOWN)
 {
  while(1)
  {
   tp_dev.scan(0);
   x = tp_dev.x[0];
   y = tp_dev.y[0];
   if(tp_dev.sta&TP_PRES_DOWN)   //触摸屏被按下
   { 
    if(tp_dev.x[0]<CHESSBOARD_END_X&&tp_dev.y[0]<CHESSBOARD_END_Y)
    { 
     if(tp_dev.x[0]>CHESSBOARD_START_X &&tp_dev.y[0]>CHESSBOARD_START_Y) 
     {
      if(((x - CHESSBOARD_START_X )%CHESSGRID_SIZE)<=7)
       xChess = (x - CHESSBOARD_START_X ) / CHESSGRID_SIZE;
      else
       xChess = ((x - CHESSBOARD_START_X ) / CHESSGRID_SIZE)+1;
      if((yChess = (y - CHESSBOARD_START_Y ) % CHESSGRID_SIZE)<7)
       yChess = (y - CHESSBOARD_START_Y ) / CHESSGRID_SIZE;
      else
       yChess = (y - CHESSBOARD_START_Y ) / CHESSGRID_SIZE+1;
//      if (g_ChessNum%2)delay_ms(300);
      if((g_ChessNum%2)==0)
       DrawChess(xChess, yChess, g_ChessNum, 1);
      State = GameOver(xChess, yChess);
      if(State==1||State==2)
      {
       POINT_COLOR = WHITE;
       LCD_DrawRectangle(18, 138, 222, 178);
       LCD_DrawRectangle(19, 139, 221, 177);
       LCD_Fill(20, 140, 220, 176, BLACK);
       BACK_COLOR = BLACK;
       switch(State)
       {
        case 1:
         Show_Str(20 + (200 - 16*2)/2, 150, 200, 16, (u8*)"和棋", 16, 0);
         break;
        case 2:
         if (g_Mode == 1)
         {
          if (g_ChessNum % 2)
          {
           POINT_COLOR=RED;
           Show_Str((240 - 16*4)/2, 150, 200, 16, (u8*)"你赢啦!!", 16, 0);
          }
          else
          {
           Show_Str((240 - 16*3)/2, 150, 200, 16, (u8*)"很遗憾 你输了", 16, 0);
          } 
         }
         delay_ms(1000);//延时提示1秒
         Recover();
         Over_test();
         break;
       }
       }
     }
    }
    else
    {
     if (x > 5 && x < 85 && y > 285 && y < 310)//重新开始
       ResStart();
     if (x > 87 && x < 135 && y > 285 && y < 310)//悔棋
     {
       if(g_Mode==1)
        RetractChess();
     }
     if(x > 137 && x < 185 && y > 285 && y < 310)
     {
       //计算AI下子坐标
       GetAIPoint(&xChess, &yChess);
        delay_ms(10);
       CHESS_Blink(xChess,yChess);
     }
     if (x > 187 && x < 236 && y > 285 && y < 310)//返回
     {
       ResStart();
       GameInit();
     }
    }
   }
   else
   {
    if(!State)
    {
     if ((g_ChessNum>0)&&(g_ChessNum%2==1))
     {
      //计算AI下子坐标
      GetAIPoint(&xChess, &yChess);
      if((g_ChessNum%2==1))
       DrawChess(xChess, yChess, g_ChessNum, 1);
      State = GameOver(xChess, yChess);
      if(State==1||State==2)
      {
       POINT_COLOR = WHITE;
       LCD_DrawRectangle(18, 138, 222, 178);
       LCD_DrawRectangle(19, 139, 221, 177);
       LCD_Fill(20, 140, 220, 176, BLACK);
       BACK_COLOR = BLACK;
       switch(State)
       {
        case 1:
         Show_Str(20 + (200 - 16*2)/2, 150, 200, 16, (u8*)"和棋", 16, 0);
         break;
        case 2:
         if (g_Mode == 1)
         {
          if (g_ChessNum % 2)
          {
           POINT_COLOR=RED;
           Show_Str((240 - 16*4)/2, 150, 200, 16, (u8*)"你赢啦!!", 16, 0);
          }
          else
          {
           Show_Str((240 - 16*3)/2, 150, 200, 16, (u8*)"很遗憾 你输了", 16, 0);
          } 
         }
         delay_ms(1000);//延时提示1秒
         Recover();
         Over_test();
         break;
       }
       }
     }
    }
    else
    {
     t++;
     if(t==10)
     {
      t=0;
     }
    }
   }
  } 
 }else delay_ms(10); //没有按键按下的时候      
}

这个函数大致分为两部分,第一部分在棋盘范围内,主要负责下棋还有判断是否连接成5颗棋子。第二部分主要是在下方按钮范围内检测并调用相关函数。同时在屏幕没有按下时来改变t的值来产生伪随机数。从而为AI下棋提供伪随机数。同时在判断如果有一方赢了的情况下提示胜利一秒,然后回到胜利后的最终界面,可以选择悔棋重新思考下棋位置,也可以选择重新开始返回主界面等。在有5颗棋子连接成的情况下点击棋盘区域不能有反应。

有一方胜利后调用函数

恢复至最终胜利界面的函数

void Recover(void)
{
 u8 m,n;
 DrawChessBoard();//恢复棋盘结束前的模样
 for (m= 0; m < 15; m++)
 {
  for (n = 0; n < 15; n++)
  {
   if (g_Chess[m][n] > 0)
   {
     int num = g_Chess[m][n];
     DrawChess(m, n, num-1, 0);
   }
  }
 }
}

在判断有一方胜利以后调用该函数,可以恢复至最终胜利界面后的函数。

void Over_test(void)
{
 int a,b;
  while(1)
  {
   tp_dev.scan(0);
   a = tp_dev.x[0];
   b = tp_dev.y[0];
   if(tp_dev.sta&TP_PRES_DOWN)   //触摸屏被按下
   { 
    if (a > 5 && a < 85 && b > 285 && b < 310)//重新开始
    {
     ResStart();
    }
    if (a > 87 && a < 135 && b > 285 && b < 310)//悔棋
    {
     RetractChess();
     Recover();
     Two_test();
    }
    if (a > 187 && a < 236 && b > 285 && b < 310)//返回
    {
      ResStart();
      GameInit();
    }    
   }
  }
}

在恢复至最终胜利界面后调用该函数,主要实现的功能是按棋盘范围内没有反应,按下方按钮才有反应。主要避免在测试界面判断胜利以后恢复至最终胜利界面依旧可以下棋。

总结

通过用STM32F103实现五子棋让我学到了很多知识,对如何完成一个工程有了更好的认识和理解,最终实现了预期的所有功能让自己很有成就感。但是这个项目也有不足之处,不能选择谁先走谁后走,后期可以再完善一下。

实际效果视频链接

基于STM32的五子棋小游戏

同时纪念一下第一次手写代码,加油!!!冲鸭!
在这里插入图片描述

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 撸撸猫 设计师:C马雯娟 返回首页