基于Visual Stdio 2017 IDE,利用EasyX图形函数库工具搭建可视化图形操作界面。通过鼠标在图形界面上点击选子,实现了五子棋的双人对战小游戏。
本程序主体框架是从https://blog.youkuaiyun.com/ArchyLi/article/details/70446314学习来的,但其功能简单、界面简陋、程序功能还有漏洞,于是做出了一下修改。
添加功能:1.增加随时退出功能、 2.增加重新开始功能、3.增加悔棋功能 4.增加总比分显示、5.增加落子提示
美化界面:从开始界面,游戏界面、到结束界面使用一些网上搜集来的图片作为背景,不再是黑白背景
修复漏洞及优化:1.修复了继续游戏时采用层层递归可能导致栈溢出的潜在问题、2.优化了判定胜利规则程序,不再是整个棋盘遍历。
2.效果展示
开始界面
正式游戏界面
结束界面
3.程序代码
采用的是C的面向过程的编程思想,以后会考虑改成C++面向对象的编程结构。关于字符集设置网上很多说设置成“多字节字符集”,但我的还是乱码。于是我将字符集设置成了“未设置”,字体就不乱码了。
#include<graphics.h>
#include<conio.h>
#include<windows.h>
#include<stdio.h>
#include<string>
#include <time.h>
#define GridSize 15
#define InternalSize 33
using namespace std;
#pragma warning(disable:4996)
void menu();
bool play();
void display();
bool playgame();
bool IsAgain(int index);
void DisplayExitGameMessage();
bool IsWin(int i, int j, int player);
int a[GridSize][GridSize] = { 0 };//棋盘标记场
IMAGE img_bk1, img_bk2;
int Player1WinCount = 0, Player2WinCount = 0;
int main()
{
//initgraph(600, 600, SHOWCONSOLE);
initgraph(600, 600);
menu();
DisplayExitGameMessage();
closegraph();
return 0;
}
void menu()
{
IMAGE img;
setaspectratio(1.1f, 1);
//loadimage(&img, (LPCTSTR)"IMAGE", (LPCTSTR)"tur", 0, 0, true);
loadimage(&img, _T(".\\五子棋1.jpg"));
putimage(0, 0, &img, SRCPAINT);
settextstyle(50, 20, (LPCTSTR)"楷体");
settextcolor(YELLOW);
setbkmode(WHITE);
outtextxy(150, 100, (LPCTSTR)"五子棋双人对战");
outtextxy(200, 400, (LPCTSTR)"开始游戏");
outtextxy(200, 451, (LPCTSTR)"退出游戏");
MOUSEMSG m;
while (true)
{
m = GetMouseMsg();
if (m.uMsg == WM_MOUSEMOVE && m.x >= 200 && m.x <= 360 && m.y >= 400 && m.y <= 450)//判断鼠标是否移动到了开始游戏,是则用黄色方框标记
{
setlinecolor(YELLOW);
rectangle(200, 400, 360, 450);
}
else {
setlinecolor(getbkcolor());
rectangle(200, 400, 360, 450);
}
if (m.uMsg == WM_MOUSEMOVE && m.x >= 200 && m.x <= 360 && m.y >= 451 && m.y <= 500)//判断鼠标是否移动到了退出游戏,是则用黄色方框标记
{
setlinecolor(YELLOW);
rectangle(200, 451, 360, 500);
}
else {
setlinecolor(getbkcolor());
rectangle(200, 451, 360, 500);
}
if (m.uMsg == WM_LBUTTONDOWN && m.x >= 200 && m.x <= 360 && m.y >= 400 && m.y <= 450)//判断是否点击开始游戏
{
cleardevice();
Sleep(200);
bool Continue_Game = true;
while (Continue_Game) {
Continue_Game = play();//开始游戏并循环判断是否继续游戏
}
break;
}
if (m.uMsg == WM_LBUTTONDOWN && m.x >= 200 && m.x <= 360 && m.y >= 451 && m.y <= 500)//判断是否点击退出游戏
{
break;
}
}
}
bool play()
{
cleardevice();
graphdefaults();
display();//显示背景棋盘
setlinecolor(WHITE);
settextstyle(20, 10, (LPCTSTR)"楷体");
settextcolor(BLACK);
setbkmode(WHITE);
outtextxy(5, 5, (LPCTSTR)"player1:黑子");
outtextxy(5, 28, (LPCTSTR)"步数: ");
outtextxy(475, 5, (LPCTSTR)"player2:白子");
outtextxy(475, 28, (LPCTSTR) "步数: ");
settextstyle(25, 15, (LPCTSTR)"楷体");//高25个像素,宽15个像素
outtextxy(475, 560, (LPCTSTR) "退出游戏");
outtextxy(10, 560, (LPCTSTR) "重新开始");
outtextxy(270, 560, (LPCTSTR) "悔棋");
return playgame();
}
void display()//显示棋盘网格
{
loadimage(&img_bk1, _T(".\\水墨画.jpg"));
putimage(0, 0, &img_bk1, SRCPAINT);
clearrectangle(50, 50, 550, 550);
loadimage(&img_bk2, _T(".\\五子棋棋盘.jpg"));
putimage(50, 50, &img_bk2, SRCPAINT);
}
bool playgame()//正式开始游戏
{
settextstyle(25, 15, (LPCTSTR)"楷体");//高25个像素,宽15个像素
string str = to_string(Player1WinCount) + " : " + to_string(Player2WinCount);
outtextxy(265, 15, (LPCTSTR)str.data());
int StepNum1 = 0, StepNum2 = 0; settextstyle(20, 10, (LPCTSTR)"楷体");
outtextxy(60, 27, (LPCTSTR)to_string(StepNum1).data());
outtextxy(530, 27, (LPCTSTR)to_string(StepNum2).data());
settextstyle(25, 15, (LPCTSTR)"楷体", 2700, 0, 0, 0, 0, 0);//高25个像素,宽15个像素
outtextxy(25, 50, (LPCTSTR)"黑方落子");
memset(a, 0, sizeof(a));
int Pre_i = -1, Pre_j = -1, Pre_C = -1, Pre_R = -1;
int player = 1; bool UndoAble = true;
MOUSEMSG msg;
while (true)
{
msg = GetMouseMsg();
//“退出游戏”区域相关操作
if (msg.uMsg == WM_MOUSEMOVE && msg.x >= 475 && msg.x <= 595 && msg.y >= 560 && msg.y <= 585)//判断鼠标是否移动到了退出游戏,是则用黄色方框标记
{
setlinecolor(YELLOW);
rectangle(475, 560, 595, 585);
}
else {
setlinecolor(BLACK);
rectangle(475, 560, 595, 585);
}
if (msg.uMsg == WM_LBUTTONDOWN && msg.x >= 460 && msg.x <= 580 && msg.y >= 550 && msg.y <= 575)//判断鼠标是否点击了退出游戏
{
return false;
}
//“重新开始”区域相关操作
if (msg.uMsg == WM_MOUSEMOVE && msg.x >= 10 && msg.x <= 130 && msg.y >= 560 && msg.y <= 585)//判断鼠标是否移动到了退出游戏,是则用黄色方框标记
{
setlinecolor(YELLOW);
rectangle(10, 560, 130, 585);
}
else {
setlinecolor(BLACK);
rectangle(10, 560, 130, 585);
}
if (msg.uMsg == WM_LBUTTONDOWN && msg.x >= 10 && msg.x <= 130 && msg.y >= 560 && msg.y <= 585)//判断鼠标是否点击了退出游戏
{
Player1WinCount = 0, Player2WinCount = 0;
return true;
}
//“悔棋”区域相关操作
if (msg.uMsg == WM_MOUSEMOVE && msg.x >= 270 && msg.x <= 330 && msg.y >= 560 && msg.y <= 585)//判断鼠标是否移动到了悔棋,是则用黄色方框标记
{
setlinecolor(YELLOW);
rectangle(270, 560, 330, 585);
}
else {
setlinecolor(BLACK);
rectangle(270, 560, 330, 585);
}
if (msg.uMsg == WM_LBUTTONDOWN && msg.x >= 270 && msg.x <= 330 && msg.y >= 560 && msg.y <= 585)//判断鼠标是否点击了悔棋
{
HWND hq = FindWindow(NULL, "五子棋双人对战");//获取父窗口句柄
if (Pre_i == -1) {
MessageBox(hq, (LPCTSTR) "目前还不能悔棋", "提示", MB_OK);
}
else if (UndoAble) {
UndoAble = false;
a[Pre_i][Pre_j] = 0;
clearcircle(Pre_C, Pre_R, 12);
putimage(Pre_C - 12, Pre_R - 12, 25, 25, &img_bk2, Pre_C - 12 - 50, Pre_R - 12 - 50, SRCCOPY);
if (player == 1) {
settextstyle(20, 10, (LPCTSTR)"楷体");
putimage(530, 27, 20, 20, &img_bk1, 530, 27, SRCCOPY);
outtextxy(530, 27, (LPCTSTR)to_string(--StepNum2).data());
settextstyle(25, 15, (LPCTSTR)"楷体", 2700, 0, 0, 0, 0, 0);//高25个像素,宽15个像素
outtextxy(575, 50, (LPCTSTR)"白方落子");
settextstyle(25, 15, (LPCTSTR)"楷体", 0, 0, 0, 0, 0, 0);//高25个像素,宽15个像素
clearrectangle(10, 70, 40, 150);
putimage(10, 70, 30 + 1, 100 + 1, &img_bk1, 10, 70, SRCCOPY);
player = 2;
}
else {
settextstyle(20, 10, (LPCTSTR)"楷体");
putimage(60, 27, 20, 20, &img_bk1, 60, 27, SRCCOPY);
outtextxy(60, 27, (LPCTSTR)to_string(--StepNum1).data());
settextstyle(25, 15, (LPCTSTR)"楷体", 2700, 0, 0, 0, 0, 0);//高25个像素,宽15个像素
outtextxy(25, 50, (LPCTSTR)"黑方落子");
settextstyle(25, 15, (LPCTSTR)"楷体", 0, 0, 0, 0, 0, 0);//高25个像素,宽15个像素
clearrectangle(560, 70, 590, 150);
putimage(560, 70, 30 + 1, 100 + 1, &img_bk1, 560, 70, SRCCOPY);
player = 1;
}
}
else {
MessageBox(hq, (LPCTSTR) "最多连续悔一次", "已经悔过棋了", MB_OK);
}
}
//棋盘区域点击操作
if (msg.uMsg == WM_LBUTTONDOWN)
{
int i = 0, j = 0;
for (int column = 71; j < GridSize; j++, j % 2 == 0 ? column += InternalSize : column += InternalSize - 1)
{
if (msg.x <= column + 10 && msg.x >= column - 10)//检测鼠标点击所在列
{
for (int row = 70; i < GridSize; i++, i % 2 == 0 ? row += InternalSize : row += InternalSize - 1)
{
if (msg.y <= row + 10 && msg.y >= row - 10)//检测鼠标点击所在行
{
Pre_i = i; Pre_j = j; UndoAble = true;
Pre_C = column; Pre_R = row;
if (player == 1 && a[i][j] == 0)
{
setfillcolor(BLACK);
solidcircle(column, row, 12);
a[i][j] = 1;
settextstyle(20, 10, (LPCTSTR)"楷体" ,0, 0, 0, 0, 0, 0);
putimage(60, 27, 20, 20, &img_bk1, 60, 27, SRCCOPY);
outtextxy(60, 27, (LPCTSTR)to_string(++StepNum1).data());
settextstyle(25, 15, (LPCTSTR)"楷体", 2700, 0, 0, 0, 0, 0);//高25个像素,宽15个像素
outtextxy(575, 50, (LPCTSTR)"白方落子");
settextstyle(25, 15, (LPCTSTR)"楷体", 0, 0, 0, 0, 0, 0);//高25个像素,宽15个像素
clearrectangle(10, 70, 40, 150);
putimage(10, 70, 30+1, 100+1, &img_bk1, 10, 70, SRCCOPY);
if (IsWin(i, j, 1)) {
settextstyle(25, 15, (LPCTSTR)"楷体");//高25个像素,宽15个像素
putimage(265, 15, 75, 25, &img_bk1, 265, 15, SRCCOPY);
str = to_string(++Player1WinCount) + " : " + to_string(Player2WinCount);
outtextxy(265, 15, (LPCTSTR)str.data());
return IsAgain(1);//判断是否赢及是否继续玩
}
player = 2;
break;
}
else if (player == 2 && a[i][j] == 0)
{
setfillcolor(WHITE);
solidcircle(column, row, 12);
a[i][j] = 2;
settextstyle(20, 10, (LPCTSTR)"楷体", 0, 0, 0, 0, 0, 0);
putimage(530, 27, 20, 20, &img_bk1, 530, 27, SRCCOPY);
outtextxy(530, 27, (LPCTSTR)to_string(++StepNum2).data());
settextstyle(25, 15, (LPCTSTR)"楷体", 2700, 0, 0, 0, 0, 0);//高25个像素,宽15个像素
outtextxy(25, 50, (LPCTSTR)"黑方落子");
settextstyle(25, 15, (LPCTSTR)"楷体", 0, 0, 0, 0, 0, 0);//高25个像素,宽15个像素
clearrectangle(560, 70, 590, 150);
putimage(560, 70, 30+1, 100+1, &img_bk1, 560, 70, SRCCOPY);
if (IsWin(i, j, 2)) {
settextstyle(25, 15, (LPCTSTR)"楷体");//高25个像素,宽15个像素
putimage(265, 15, 75, 25, &img_bk1, 265, 15, SRCCOPY);
str = to_string(Player1WinCount) + " : " + to_string(++Player2WinCount);
outtextxy(265, 15, (LPCTSTR)str.data());
return IsAgain(2);//判断是否赢及是否继续玩
}
player = 1;
break;
}
}
}
break;
}
}
}
}
}
bool IsWin(int i, int j, int player) {
int count = 1;
//判断纵向是否有五子连珠
for (int index_i = i - 1, index_j = j; index_i >= 0 && a[index_i][index_j] == player; index_i--) count++;
for (int index_i = i + 1, index_j = j; index_i < GridSize && a[index_i][index_j] == player; index_i++) count++;
if (count >= 5) return true;
else count = 1;
//判断横向是否有五子连珠
for (int index_i = i, index_j = j - 1; index_j >= 0 && a[index_i][index_j] == player; index_j--) count++;
for (int index_i = i, index_j = j + 1; index_j < GridSize && a[index_i][index_j] == player; index_j++) count++;
if (count >= 5) return true;
else count = 1;
//判断135度斜轴是否有五子连珠
for (int index_i = i - 1, index_j = j - 1; index_i >= 0 && index_j >= 0 && a[index_i][index_j] == player; index_i--, index_j--) count++;
for (int index_i = i + 1, index_j = j + 1; index_i < GridSize && index_j < GridSize&& a[index_i][index_j] == player; index_i++, index_j++) count++;
if (count >= 5) return true;
else count = 1;
//判断45度斜轴是否有五子连珠
for (int index_i = i - 1, index_j = j + 1; index_i >= 0 && index_j < GridSize && a[index_i][index_j] == player; index_i--, index_j++) count++;
for (int index_i = i + 1, index_j = j - 1; index_i < GridSize && index_j >= 0 && a[index_i][index_j] == player; index_i++, index_j--) count++;
if (count >= 5) return true;
else count = 1;
return false;
}
bool IsAgain(int index)
{
settextstyle(20, 10, (LPCTSTR)"楷体");
settextcolor(YELLOW);
if (index == 1) {
outtextxy(250, 50, (LPCTSTR) "player1 win!");
}
else {
outtextxy(250, 50, (LPCTSTR) "player2 win!");
}
HWND hq = FindWindow(NULL, "五子棋双人对战");//获取父窗口句柄
string str = "Player" + to_string(index) + " Win";
int quit = MessageBox(hq, (LPCTSTR) "是否继续游戏", (LPCTSTR)str.data(), MB_YESNO);
if (IDYES == quit) return true;
else return false;
}
void DisplayExitGameMessage() {
cleardevice();
IMAGE img;
loadimage(&img, _T(".\\水墨画2.jpg"));
putimage(0, 0, &img, SRCPAINT);
settextcolor(BLACK);
settextstyle(50, 20, (LPCTSTR)"楷体");
outtextxy(160, 80, (LPCTSTR)"欢迎下次再来!");
Sleep(1500);
}
4.问题与不足
待解决的问题:1.没有实现人机对战功能、2.不能两台电脑之间通信对战。
不足:1.程序比较冗余(需优化) 、2.变量管理复杂,经常遇到改一个变量要多处手动修改
5.附录图片
程序中用到的背景图图片直接跟源文件放到同一目录下即可
水墨画
水墨画2
五子棋1
五子棋棋盘