完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
1个回答
|
|
前言
之前写过一篇文章是关于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的值来改变按钮的背景颜色 [tr]sta=1sta=2sta=0[/tr]
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); } 对战界面 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(); } 设置界面 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] 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); } } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 在判断有一方胜利以后调用该函数,可以恢复至最终胜利界面后的函数。 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的五子棋小游戏 同时纪念一下第一次手写代码,加油!!!冲鸭! |
||
|
||
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1763 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1617 浏览 1 评论
1059 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
723 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1670 浏览 2 评论
1932浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
725浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
564浏览 3评论
591浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
549浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-20 20:43 , Processed in 0.700832 second(s), Total 77, Slave 61 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号