五子棋-完美解决闪屏问题版-新增悔棋功能(C++实现)

news/2024/10/17 18:29:37/

在学C++的时候,在做课设的时候做了一个五子棋课设,当时选这个就是觉得挺好玩的,想理解一下游戏是如何实现的,然后就入了这个坑。。。这个我没有用图形化界面来实现,完全是使用字符来实现。然后没有用图形化界面的最大的问题就是闪屏。。。因为普通的实现一般是用到清屏命令,每一次刷新输出的时候都要时间,所以就会闪屏,看着这一闪一闪的屏幕的确令有强迫症的我看到很难受,然后在全部完成五子棋的功能之后,整整花了两三天时间去解决这个问题,最终还是解决了。废话不说那么多了,直接开始吧哈哈哈。
功能介绍:
(1)开始界面和界面大小的控制 在游戏运行开始的时候,出现一个初始化界面 ,介绍游戏名字、游戏背景选择项(使用system()来改变控制台背景颜色使用sleep()来实现提示语的先后出现 ,使用字符串格式化函数sprintf()函数来实现控制台窗口的大小)。
(2)下棋操作 先通过键盘的上下左右箭头和WASD的字母移动下棋的位置,按空格键进行下棋(通过getch()函数和switch语句实现)。
(3)人人对战功能 玩家可通过先后移动棋子坐标位置进行下棋,进而进行对战游戏,直至分出胜负,在游戏期间玩家可以按n来实现重新游戏,按Esc退出游戏,可以在下方看到该是哪方下棋(通过坐标值的变化然后重新打印一张棋盘来实现光标的移动,这里用到双缓存技术来解决system(“cls”)的闪屏问题)。
(4)判断玩家棋子位置的合法性 玩家通过键盘的输入确定棋子位置坐标,系统会人性化判断棋子的合法性。若棋子不合法,玩家需要再次输入棋子位置坐标;若棋子合法,未决出胜负则继续由另一玩家确定棋子坐标。
(5)胜负判断功能 游戏系统程序可以通过棋盘上棋子的横竖方向上相同玩家棋子数量的关系,判断玩家棋子是否已构成五连子。若棋子构成五连子,则可选择结束游戏或者再来一局;若棋子没有构成五连子,则玩家可继续游戏。
(6)结束界面 玩家分出胜负之后,出现结束界面,玩家可选择是否继续玩游戏。若玩家选择继续游戏,则会重新出现空白棋盘;若玩家选择不继续游戏,则退出游戏界面。
(7)悔棋功能实现(新增) 实现原理:利用栈,在下子即按下空格键的时候,将当前子的坐标压栈,我是用了两个栈,一个记录X坐标,一个记录Y坐标,即X.push(g_cursorX); Y.push(g_cursorY);然后在按下悔棋键的时候,将栈顶的X,Y坐标赋值为0,即g_checkboard[X.top()][Y.top()] = 0;代码修改见文末。

五子棋基本的实现并不难,认真的看代码的话都会看的懂,最难的部分就是解决闪屏问题那部分,我当时也尝试了很多弯路,想要真正了解原理对于初学者并不容易,因为涉及到了比较底层的东西,但是不了解并影响实现,基本就是一个模板,要改的地方就是输出内容那个地方。我当初就是经过不断找资料不断地尝试,然后就意外的成功了,也算是比较幸运吧。所以叫我解释如何实现双缓冲我也说不清楚,上一个我参考过得链接吧。希望对你们有帮助。
https://blog.csdn.net/z1832729975/article/details/88382018
完整代码(很多地方都有注释了,所以我也不多说了直接看代码吧,有不懂或者改进的建议欢迎留言):
tips:这个程序在旧版控制台输出更加完美,因为旧版和新版的符号格式有些不一样,一般没有设置过的话都是新版的控制台,要设置的话也很简单,在输出框上方白色的地方右键,然后选择属性,选项的界面中勾上使用旧版控制台退出重新运行即可。

#include<iostream>
#include<stdlib.h>
#include<windows.h>
#include<conio.h>
using namespace std;#define CHECKBROADSIZE 25			//棋盘的大小class Rungame  //运行游戏:绘制棋盘 
{
private:int g_checkboard[CHECKBROADSIZE][CHECKBROADSIZE];char data[CHECKBROADSIZE][CHECKBROADSIZE];			//存放并输出棋盘组成符号int g_currentGamer;									//当前玩家下子  1代表黑子,2代表白子int g_cursorX, g_cursorY;							 // 坐标
public:void DrawCheckborad();	//绘制棋盘void Init();			//棋盘初始化int RunGame();int Put();              //下子成功  成功返回1 失败返回0int Check();            //判断输赢void buffer();			//双缓冲实现函数void blackground();     //背景颜色设置函数};int zhixiang_hOutput = 0;  //通过指针轮流指向两个缓冲区,实现双缓冲
HANDLE hOutput, hOutBuf;   //控制台屏幕缓冲区句柄
HANDLE *houtpoint;
COORD coord = { 6, 6 };    //起始点坐标
DWORD bytes = 0;		   //DWORD 双字即为4个字节,每个字节是8位,共32位,变量类型的内存占位int main()
{Rungame chessboard;Rungame rungame;chessboard.blackground();	chessboard.buffer();SetConsoleTitle(L"五子棋人人对战");			//设置控制台的标题//进入游戏while (1){rungame.RunGame();								//运行函数}return 0;
}
void Rungame::blackground()      //设置背景函数
{//设置控制台窗口大小char stCmd[32];sprintf(stCmd, "mode con cols=%d lines=%d", CHECKBROADSIZE + 35, CHECKBROADSIZE + 8);  //格式化控制台窗口函数system(stCmd);puts("*****************welcome to wuziqi world!******************");Sleep(500);puts("please press  'w' to turn to the white background...");Sleep(400);puts("please press  'g' to turn to the green background...");Sleep(300);puts("please press  'b' to turn to the blue background...");Sleep(200);puts("please press  'r' to turn to the red background...");Sleep(100);puts("please press  any other key to turn to the black background.");//控制控制台的颜色输出 如8代表输出灰色背景 0为黑色字体switch (getch()){case'g':system("color 0A");break;case'w':system("color F0");break;case 'b':system("color F9");break;case 'r':system("color 0C");break;}
}
int Rungame::RunGame()
{Init();				//初始化int nInput = 0;		//键盘输入值int nWinner = 0;    //赢家while (1){//system("cls");				//清屏   导致头上冒出一串东西DrawCheckborad();				//绘制棋盘switch (getch()){case 32: //space{// 1.下子 能不能下if (Put()){//2.判断输赢,每下一颗子判断一次输赢nWinner = Check();//更换玩家g_currentGamer = 3 - g_currentGamer;if (nWinner == 1){if (MessageBox(NULL, L"'■'方胜,是否再来一局?", L"五子棋", MB_YESNO) == IDYES){RunGame();      //重新开始游戏}}else if (nWinner == 2){if (MessageBox(NULL, L"'○'方胜,是否再来一局?", L"五子棋", MB_YESNO) == IDYES)RunGame();      //重新开始游戏}}}break;case 75: //向左case 'a':g_cursorY--;if (g_cursorY < 0)     //到最左的时候从最右边出来g_cursorY = 0;break;case 77: //向右case'd':g_cursorY++;if (g_cursorY > CHECKBROADSIZE - 1)g_cursorY = CHECKBROADSIZE - 1;break;case 72://向上case 'w':g_cursorX--;if (g_cursorX < 0)g_cursorX = 0;break;case 80://向下case 's':g_cursorX++;if (g_cursorX > CHECKBROADSIZE - 1)g_cursorX = CHECKBROADSIZE - 1;break;case 'n':					//按下n重新游戏//提示信息if (MessageBox(NULL, L"是否要重新开始游戏?", L"五子棋", MB_YESNO) == IDYES){RunGame();}break;case 27://提示信息if (MessageBox(NULL, L"是否要退出游戏?", L"五子棋", MB_YESNO) == IDYES){exit(0);}break;}}return 0;
}
void Rungame::Init()
{Rungame rungame;memset(g_checkboard, 0, sizeof(g_checkboard));		//清空二维数组为0在下子的时候判断g_currentGamer = 1;   //黑子先下g_cursorX = CHECKBROADSIZE / 2;						//开始游戏的时候光标在棋盘中间g_cursorY = CHECKBROADSIZE / 2;						
}
void Rungame::DrawCheckborad()
{//输出提示信息char Tips[] = "please press 'w' 'a' 's' 'd' to control the cursor... ";char Tips1[] = "press 'n' to have a new game... ";char Tips2[] = "press 'space' to play the chess... ";char Tips3[] = "press 'Esc' to exit the game... ";char score[50];for (int i = 0; i < CHECKBROADSIZE; i++)   //控制行{for (int j = 0; j < CHECKBROADSIZE; j++) //控制列{if (i == g_cursorX && j == g_cursorY)data[i][j] = 206;	//╬else if (g_checkboard[i][j] == 1)   //黑子data[i][j] = 219;    //■else if (g_checkboard[i][j] == 2)   //白子data[i][j] = 9;      //○else if (i == 0 && j == 0)data[i][j] = 218;	//┏else if (i == 0 && j == CHECKBROADSIZE - 1)data[i][j] = 191;	//┓else if (i == CHECKBROADSIZE - 1 && j == 0)data[i][j] = 192;	//┗else if (i == CHECKBROADSIZE - 1 && j == CHECKBROADSIZE - 1)data[i][j] = 217;	//┛else if (i == 0)data[i][j] = 194;	//┳else if (i == CHECKBROADSIZE - 1)data[i][j] = 193;	//┻else if (j == 0)data[i][j] = 195;	//┣else if (j == CHECKBROADSIZE - 1)data[i][j] = 180;	//┫elsedata[i][j] = 197;	//┼}cout << endl;}//实现双缓冲//用指针实现指向两个缓冲区zhixiang_hOutput = !zhixiang_hOutput;if (!zhixiang_hOutput){houtpoint = &hOutput;}else{houtpoint = &hOutBuf;}coord.Y = 1;  //起始坐标//以下分别输出三个TipsWriteConsoleOutputCharacterA(*houtpoint, Tips, strlen(Tips), coord, &bytes);    // 在指定位置处插入指定数量的字符coord.Y++;				//换一行显示WriteConsoleOutputCharacterA(*houtpoint, Tips2, strlen(Tips2), coord, &bytes);coord.Y++;WriteConsoleOutputCharacterA(*houtpoint, Tips1, strlen(Tips1), coord, &bytes);coord.Y++;WriteConsoleOutputCharacterA(*houtpoint, Tips3, strlen(Tips3), coord, &bytes);coord.Y++;for (int i = 0; i < CHECKBROADSIZE; i++)          //绘制棋盘的符号{coord.Y++;WriteConsoleOutputCharacterA(*houtpoint, (char *)data[i], CHECKBROADSIZE, coord, &bytes);}//显示到哪方下sprintf(score, "current player: %d  Tips: 1:square  2:circle ", g_currentGamer);coord.Y++;		//换一行WriteConsoleOutputCharacterA(*houtpoint, score, strlen(score), coord, &bytes);SetConsoleActiveScreenBuffer(*houtpoint);
}
void Rungame::buffer()
{//实现双缓冲SetConsoleOutputCP(437);//改成标准英文输出标志,这样可以使用扩展ASCII码表,以方便后面游戏界面的制作//创建新的控制台缓冲区hOutBuf = CreateConsoleScreenBuffer(GENERIC_WRITE,  //定义进程可以往缓冲区写数据FILE_SHARE_WRITE, //定义缓冲区可共享写权限NULL,CONSOLE_TEXTMODE_BUFFER,NULL);hOutput = CreateConsoleScreenBuffer(GENERIC_WRITE,        //定义进程可以往缓冲区写数据FILE_SHARE_WRITE,     //定义缓冲区可共享写权限NULL,CONSOLE_TEXTMODE_BUFFER,NULL);//隐藏两个缓冲区的光标CONSOLE_CURSOR_INFO cci;cci.bVisible = 0;cci.dwSize = 1;SetConsoleCursorInfo(hOutput, &cci);SetConsoleCursorInfo(hOutBuf, &cci);}
int Rungame::Put()              //下子成功  成功返回1 失败返回0
{//怎么才是下子成功,而且改下那颗子if (g_checkboard[g_cursorX][g_cursorY] == 0)    //下子成功{g_checkboard[g_cursorX][g_cursorY] = g_currentGamer;return 1;}elsereturn 0;
}
int Rungame::Check()       //判断输赢
{int nHorizontal = 1;   //水平方向int nVertical = 1;     //垂直方向int nPostiveDirection = 1;  //正斜向int nSkewDirection = 1;   //反斜向//水平  向右检查//g_cursorY 列的变化for (int i = 1; i < 5; i++){if (g_cursorY + i < CHECKBROADSIZE && g_checkboard[g_cursorX][g_cursorY + i] == g_currentGamer)nHorizontal++;elsebreak;}//水平 向左检查for (int i = 1; i < 5; i++){if (g_cursorY - i > 0 && g_checkboard[g_cursorX][g_cursorY - i] == g_currentGamer)nHorizontal++;elsebreak;}if (nHorizontal >= 5){g_cursorY += 1;return g_currentGamer;}//垂直 向下检查for (int i = 1; i < 5; i++){if (g_cursorX + i < CHECKBROADSIZE && g_checkboard[g_cursorX + i][g_cursorY] == g_currentGamer)nVertical++;elsebreak;}// 向上检查for (int i = 1; i < 5; i++){if (g_cursorX - i > 0 && g_checkboard[g_cursorX - i][g_cursorY] == g_currentGamer)nVertical++;elsebreak;}if (nVertical >= 5){g_cursorY += 1;return g_currentGamer;}//正斜向上 检查for (int i = 1; i < 5; i++){if (g_cursorX - i > 0 && g_cursorY + i < CHECKBROADSIZE && g_checkboard[g_cursorX - i][g_cursorY + i] == g_currentGamer)nPostiveDirection++;elsebreak;}//正斜向下检查for (int i = 1; i < 5; i++){if (g_cursorY - i > 0 && g_cursorX + i < CHECKBROADSIZE && g_checkboard[g_cursorX + i][g_cursorY - i] == g_currentGamer)nPostiveDirection++;elsebreak;}if (nPostiveDirection >= 5){g_cursorY += 1;return g_currentGamer;}//反斜向下检查for (int i = 1; i < 5; i++){if (g_cursorX + i < CHECKBROADSIZE && g_cursorY + i < CHECKBROADSIZE && g_checkboard[g_cursorX + i][g_cursorY + i] == g_currentGamer)nSkewDirection++;elsebreak;}//正斜向上检查for (int i = 1; i < 5; i++){if (g_cursorX - i > 0 && g_cursorY - i > 0 && g_checkboard[g_cursorX - i][g_cursorY - i] == g_currentGamer)nSkewDirection++;elsebreak;}if (nSkewDirection >= 5){g_cursorY += 1;return g_currentGamer;}return 0;
}

效果图(放不了视频,就放张图片吧):
在这里插入图片描述
悔棋功能实现要修改的地方(在源代码中修改这些地方即可)
1.加入头文件 #include<stack>
2.在Rungame类中添加记录X,Y坐标的栈变量和记录悔棋次数的变量
class
3.修改RunGame()函数
a.每一次下棋都将坐标压栈,下一次棋就重置Retract_Time,这样在每一次下棋之后都可以悔一次棋
在这里插入图片描述
b.添加悔棋键(r)
也可以自己定义悔棋次数,修改if中的Retract_Time的限制条件即可
在这里插入图片描述
效果:
1
2
3


http://www.ppmy.cn/news/563050.html

相关文章

C++、easyx组合的界面版五子棋(适合新手)

C、easyx组合的五子棋界面版&#xff08;适合新手&#xff09; 点击进入五子棋控制台版本 文章目录 C、easyx组合的五子棋界面版&#xff08;适合新手&#xff09;前言效果图一、游戏规则二、实现逻辑1.绘制棋盘2.落子2.1 鼠标坐标的获取2.2 绘制棋子 三、输赢判定算法四、其…

Windows编程 简易五子棋

简易五子棋&#xff0c;望指正 #include <windows.h> #include<commctrl.h> #define SIZE 20 UINT IDC_BUTTON1200; HWND hwnd; HPEN penbackCreatePen(PS_SOLID,1,RGB(0,0,0)); HPEN penback2CreatePen(PS_SOLID,1,RGB(255,255,255)); HBRUSH brushbackCreateSo…

课程设计-单机版五子棋游戏-Java

一. 项目简介&#xff1a; 五子棋是全国智力运动会竞技项目之一&#xff0c;是一种两人对弈的纯策略型棋类游戏。五子棋的玩法是&#xff1a;双方分别使用黑白两色的棋子&#xff0c;下在棋盘直线与横线的交叉点上&#xff0c;先形成五子连线者获胜。五子棋的棋具与围棋通用…

五子棋人机对弈代码——java版

算法是穷举递归法&#xff0c;只不过用java重新写了一遍 import java.awt.Color;import java.awt.Container;import java.awt.Graphics;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.awt.event.MouseEvent;import java.awt.event.Mou…

c++五子棋人机版

五子棋游戏人机版&#xff1a; 电脑实现了阻挡对方连续的棋子 #include<iostream> #include<iomanip> using namespace std; #include<stdlib.h> #include<time.h> /*class PerPlayer { public:int m;int n;int person(); }; PerPlayer pp;class CompP…

五子棋程序设计实现技术文档

五子棋程序设计实现文档 文章目录 五子棋程序设计实现文档前言一、运行截图二、基本思路1.实现过程2.落子3.悔棋4.人机对战的实现1.机器人落子逻辑**2.改进胜负判断方法3.计算目标点的权值(白棋ai使用)4.计算目标点的权值(黑棋ai使用)** 4.扩展功能1.智能“提示”功能 前言 博…

Windows平台下C++五子棋项目实战开发

1. 项目目标 2. 效果演示 3. 创建项目 4. 项目框架设计 4.1 设计项目框架 4.2 根据设计框架创建类 5. 给类添加主要接口 5.1 设计棋盘类Chess的主要接口 5.2 设计AI类的主要接口 5.3 设计Man类的主要接口 5.4 设计ChessGame的主要接口 5.5 添加各个接口的具体实现 6. 实…

基于java的五子棋游戏设计

技术&#xff1a;Java、JSP等摘要&#xff1a;随着互联网迅速的发展&#xff0c;网络游戏已经成为人们普遍生活中不可或缺的一部分&#xff0c;它不仅能使人娱乐&#xff0c;也能够开发人的智力&#xff0c;就像本文所主要讲的五子棋游戏一样能挖掘人们聪明的才干与脑袋的机灵程…