首先需要准备编程环境,建议使用vs2022,因为easyx图形库可以一键安装在vs上,比较方便。
easyx官网:EasyX Graphics Library for C++
参考网上其他人的代码,发现很多是使用检测鼠标位置和点击实现落子,我认为比较麻烦,此项目采用使用上下左右按键移动落子框,按下enter实现落子。
界面,棋子绘制
首先先来定义一些我们需要的常量,导入必须的包。
#include <Windows.h>//包含弹窗
#include <iostream>
#include <graphics.h>
#define CHESSSIZE 10//棋子半径
#define GAP 40 //棋盘线之间的间隔
#define INITIALX 40 //初始x值
#define INITIALY 40 //初始y值
#define BOXSIZE 20 //落子框大小
using namespace std;
再来绘制棋盘,这里绘制的是15*15的棋盘,左上角坐标为(40,40)行列间隔为40。
//绘制棋盘
for (int i = GAP; i <= 600; i += GAP)
{
line(i, GAP, i, 600);
}
for (int j = GAP; j <= 600; j += GAP)
{
line(GAP, j, 600, j);
}
接着是落子框的绘制,落子框中心需要在线的交点上,初始落子框设定在棋盘左上角。
先定义一个绘制函数
void DrawBox(int lx,int ly,int rx,int ry)
{
rectangle(lx, ly, rx, ry);
}
上方函数中,recangle();为easyx中绘制不填充方框的函数。
函数将会在以后的主循环中调用。
方框还需要移动,这里定义一个结构体来方便以后实现方框移动操作。
struct Box
{
int lx = INITIALX - BOXSIZE;
int ly = INITIALY - BOXSIZE;
int rx = INITIALX + BOXSIZE;
int ry = INITIALY + BOXSIZE;
};
下面是定义绘画单个棋子的函数
void DrawChess(int x,int y,int r,bool type)
{
//true为实心,false为空心(true为白方,false为黑方)
if (type)
{
fillcircle(x, y, r);
}else
{
circle(x, y, r);
}
}
调用时,传入棋子的x,y坐标以及半径r,类型type, type用于判断是黑子还是白子。
到这里,基本要素差不多齐全了
棋子储存
如何存储棋子并判断胜负呢?
这里采用一个数组来存储,通过遍历数组来判断胜负。
int main()
{
initgraph(640, 640);
char board[15][15] = { 0 };//储存棋子
Box movebox;
bool chess_type = false;//判断棋子类型
bool keyPressed = false; // 按键状态
...
...
...
}
逐行解释代码:
首先使用initgraph()函数创建一个640*640的窗口
然后定义我们的棋盘数组
接着是定义移动落子框
初始化棋子类型为false(这里规定false为黑子)
检测按键是否按下(easyx有消息处理函数,但貌似按键按下和弹起均会检测,就导致按下一次按键会执行两次程序,这里加入按键检测确保按下一次按键只执行一次程序,如果有更好的办法欢迎讨论。)
然后便是游戏的主要部分:主循环
...
...
...
while (1)
{
cleardevice();
BeginBatchDraw();
DrawBoard(board);//绘制棋盘和棋子
DrawBox(movebox.lx, movebox.ly, movebox.rx, movebox.ry);//绘制落子框
EndBatchDraw();
ExMessage mes = getmessage(EX_KEY);
...
...
...
}
这里采用BeginBatchDraw();与BeginBatchDraw();双缓冲来防止闪屏,使用mes接收键盘输入信息。
DrawBoard();函数用于绘画棋盘和棋子,棋盘绘制代码已在上方给出,我们需要不断地绘制场上已有的棋子,绘制代码会在下面给出。
检测用户输入,落子
方框的移动与落子操作,使用switch实现。
switch (mes.vkcode)
{
case VK_UP:
if (movebox.ly >= INITIALY && !keyPressed)
{
movebox.ly -= BOXSIZE*2;
movebox.ry -= BOXSIZE*2;
keyPressed = true; // 记录按下状态
}
break;
case VK_DOWN:
if (movebox.ry <= 600 && !keyPressed)
{
movebox.ly += BOXSIZE*2;
movebox.ry += BOXSIZE*2;
keyPressed = true;
}
break;
case VK_LEFT:
if (movebox.lx >= INITIALX && !keyPressed)
{
movebox.lx -= BOXSIZE*2;
movebox.rx -= BOXSIZE*2;
keyPressed = true;
}
break;
case VK_RIGHT:
if (movebox.rx <= 600 && !keyPressed)
{
movebox.lx += BOXSIZE*2;
movebox.rx += BOXSIZE*2;
keyPressed = true;
}
break;
case VK_RETURN://落子
if (!keyPressed)
{
int x = (movebox.lx - BOXSIZE) / INITIALX;
int y = (movebox.ly - BOXSIZE) / INITIALX;
if (board[y][x] == 0)// 检查是否已有棋子
{
board[y][x] = chess_type ? 1 : 2; // 1为白棋,2为黑棋
chess_type = !chess_type; // 切换棋子类型
}
keyPressed = true;
}
}
方框的移动为前几个case语句,落子为最后一个case语句。
case判断的是虚拟键码,可以按照虚拟键码表更改。虚拟键码 (Winuser.h) - Win32 apps | Microsoft Learn
这里采用if语句限制方框的移动,防止方框移出棋盘同时检测键盘按下状态,防止switch执行两次。
在用户按下enter键后,会检测数组该位置是否有棋子,若没有,则按照棋子类型落子,落子后切换棋子类型,最后更改按键信息。
关于落子位置,采用读取方框边角的坐标,将坐标加减BOXSIZE的二倍来定位到棋盘交点。
利用前面定义的board数组来存储我们的棋子信息,使用1来代表白棋,2来代表黑棋。
循环绘制
我们需要循环绘制我们的棋盘和棋子,使其显示在屏幕上,我们可以遍历数组,来找到棋子的类型,通过计算找到棋子位置。
绘制棋子:
for (int i = 0;i < 15; ++i)
{
for (int j = 0;j < 15; ++j)
{
if (board[i][j] == 1)
{
DrawChess(INITIALX + j * BOXSIZE*2, INITIALY + i * BOXSIZE*2, CHESSSIZE, true);
}
else if (board[i][j] == 2)
{
DrawChess(INITIALX + j * BOXSIZE*2, INITIALY + i * BOXSIZE*2, CHESSSIZE, false);
}
}
}
将前面的棋盘绘制代码与棋子绘制代码放入 DrawBoard();函数中
void DrawBoard(char board[15][15])
{
//绘制棋盘
for (int i = GAP; i <= 600; i += GAP)
{
line(i, GAP, i, 600);
}
for (int j = GAP; j <= 600; j += GAP)
{
line(GAP, j, 600, j);
}
//绘制已有棋子
for (int i = 0;i < 15; ++i)
{
for (int j = 0;j < 15; ++j)
{
if (board[i][j] == 1)
{
DrawChess(INITIALX + j * BOXSIZE*2, INITIALY + i * BOXSIZE*2, CHESSSIZE, true);
}
else if (board[i][j] == 2)
{
DrawChess(INITIALX + j * BOXSIZE*2, INITIALY + i * BOXSIZE*2, CHESSSIZE, false);
}
}
}
}
胜负判断
现在已经能实现在棋盘任意处落子的操作了,接下来就是胜负判断,这里依然写一个胜负判断函数,方便以后调用。
int VectoryCheck(char board[15][15])
{
//直线检测
for (int i = 0; i < 15; ++i)
{
for (int j = 0; j < 11; ++j)
{
if (board[i][j] == 1 && board[i][j + 1] == 1 && board[i][j + 2] == 1 && board[i][j + 3] == 1 && board[i][j + 4] == 1)
{
return 1;//白棋获胜
}
else if(board[i][j] == 2 && board[i][j + 1] == 2 && board[i][j + 2] == 2 && board[i][j + 3] == 2 && board[i][j + 4] == 2)
{
return 2;//黑棋获胜
}
}
}
// 垂直检测
for (int j = 0; j < 15; ++j)
{
for (int i = 0; i < 11; ++i)
{
if (board[i][j] == 1 && board[i + 1][j] == 1 && board[i + 2][j] == 1 && board[i + 3][j] == 1 && board[i + 4][j] == 1)
{
return 1;
}
else if (board[i][j] == 2 && board[i + 1][j] == 2 && board[i + 2][j] == 2 && board[i + 3][j] == 2 && board[i + 4][j] == 2)
{
return 2;
}
}
}
// 斜向检测(从左上到右下)
for (int i = 0; i < 11; ++i)
{
for (int j = 0; j < 11; ++j)
{
if (board[i][j] == 1 && board[i + 1][j + 1] == 1 && board[i + 2][j + 2] == 1 && board[i + 3][j + 3] == 1 && board[i + 4][j + 4] == 1)
{
return 1; // 白棋获胜
}
else if (board[i][j] == 2 && board[i + 1][j + 1] == 2 && board[i + 2][j + 2] == 2 && board[i + 3][j + 3] == 2 && board[i + 4][j + 4] == 2)
{
return 2; // 黑棋获胜
}
}
}
// 斜向检测(从右上到左下)
for (int i = 0; i < 11; ++i)
{
for (int j = 4; j < 15; ++j)
{
if (board[i][j] == 1 && board[i + 1][j - 1] == 1 && board[i + 2][j - 2] == 1 && board[i + 3][j - 3] == 1 && board[i + 4][j - 4] == 1)
{
return 1; // 白棋获胜
}
else if (board[i][j] == 2 && board[i + 1][j - 1] == 2 && board[i + 2][j - 2] == 2 && board[i + 3][j - 3] == 2 && board[i + 4][j - 4] == 2)
{
return 2; // 黑棋获胜
}
}
}
return false;
}
使用for循环遍历数组,检测横向,纵向,斜向是否有相同的五个棋子。注意初始化i和j的值以及判断条件,不要超出数组范围。
利用返回值判断胜负,返回1则白棋胜,返回2则黑棋胜。
if (VectoryCheck(board) == 1)
{
MessageBox(NULL, "白方胜利!", "结束", MB_SYSTEMMODAL);
closegraph();
}
else if (VectoryCheck(board) == 2)
{
MessageBox(NULL, "黑方胜利!", "结束", MB_SYSTEMMODAL);
closegraph();
}
胜利后弹出弹窗并关闭窗口。
之后在主函数调用胜负判断函数,加上按键释放的判断就行了
按键释放判断:
if (mes.message == WM_KEYUP)
{
keyPressed = false; // 释放按键时重置状态
}
完整代码
#include <Windows.h>//包含弹窗
#include <iostream>
#include <graphics.h>
#define CHESSSIZE 10
#define GAP 40
#define INITIALX 40
#define INITIALY 40
#define BOXSIZE 20
using namespace std;
struct Box
{
int lx = INITIALX - BOXSIZE;
int ly = INITIALY - BOXSIZE;
int rx = INITIALX + BOXSIZE;
int ry = INITIALY + BOXSIZE;
};
void DrawChess(int x,int y,int r,bool type)
{
//true为实心,false为空心(true为白方,false为黑方)
if (type)
{
fillcircle(x, y, r);
}else
{
circle(x, y, r);
}
}
void DrawBox(int lx,int ly,int rx,int ry)
{
rectangle(lx, ly, rx, ry);
}
void DrawBoard(char board[15][15])
{
//绘制棋盘
for (int i = GAP; i <= 600; i += GAP)
{
line(i, GAP, i, 600);
}
for (int j = GAP; j <= 600; j += GAP)
{
line(GAP, j, 600, j);
}
//绘制已有棋子
for (int i = 0;i < 15; ++i)
{
for (int j = 0;j < 15; ++j)
{
if (board[i][j] == 1)
{
DrawChess(INITIALX + j * BOXSIZE*2, INITIALY + i * BOXSIZE*2, CHESSSIZE, true);
}
else if (board[i][j] == 2)
{
DrawChess(INITIALX + j * BOXSIZE*2, INITIALY + i * BOXSIZE*2, CHESSSIZE, false);
}
}
}
}
int VectoryCheck(char board[15][15])
{
//直线检测
for (int i = 0; i < 15; ++i)
{
for (int j = 0; j < 11; ++j)
{
if (board[i][j] == 1 && board[i][j + 1] == 1 && board[i][j + 2] == 1 && board[i][j + 3] == 1 && board[i][j + 4] == 1)
{
return 1;//白棋获胜
}
else if(board[i][j] == 2 && board[i][j + 1] == 2 && board[i][j + 2] == 2 && board[i][j + 3] == 2 && board[i][j + 4] == 2)
{
return 2;//黑棋获胜
}
}
}
// 垂直检测
for (int j = 0; j < 15; ++j)
{
for (int i = 0; i < 11; ++i)
{
if (board[i][j] == 1 && board[i + 1][j] == 1 && board[i + 2][j] == 1 && board[i + 3][j] == 1 && board[i + 4][j] == 1)
{
return 1;
}
else if (board[i][j] == 2 && board[i + 1][j] == 2 && board[i + 2][j] == 2 && board[i + 3][j] == 2 && board[i + 4][j] == 2)
{
return 2;
}
}
}
// 斜向检测(从左上到右下)
for (int i = 0; i < 11; ++i)
{
for (int j = 0; j < 11; ++j)
{
if (board[i][j] == 1 && board[i + 1][j + 1] == 1 && board[i + 2][j + 2] == 1 && board[i + 3][j + 3] == 1 && board[i + 4][j + 4] == 1)
{
return 1; // 白棋获胜
}
else if (board[i][j] == 2 && board[i + 1][j + 1] == 2 && board[i + 2][j + 2] == 2 && board[i + 3][j + 3] == 2 && board[i + 4][j + 4] == 2)
{
return 2; // 黑棋获胜
}
}
}
// 斜向检测(从右上到左下)
for (int i = 0; i < 11; ++i)
{
for (int j = 4; j < 15; ++j)
{
if (board[i][j] == 1 && board[i + 1][j - 1] == 1 && board[i + 2][j - 2] == 1 && board[i + 3][j - 3] == 1 && board[i + 4][j - 4] == 1)
{
return 1; // 白棋获胜
}
else if (board[i][j] == 2 && board[i + 1][j - 1] == 2 && board[i + 2][j - 2] == 2 && board[i + 3][j - 3] == 2 && board[i + 4][j - 4] == 2)
{
return 2; // 黑棋获胜
}
}
}
return false;
}
int main()
{
initgraph(640, 640);
char board[15][15] = { 0 };
Box movebox;
bool chess_type = false;//判断棋子类型
bool keyPressed = false; // 按键状态,由于按键的按下和弹起均会被检测,会导致switch语句被执行两次,故加入按键检测。
while (1)
{
cleardevice();
BeginBatchDraw();
DrawBoard(board);//绘制棋盘和棋子
DrawBox(movebox.lx, movebox.ly, movebox.rx, movebox.ry);
EndBatchDraw();
ExMessage mes = getmessage(EX_KEY);
//控制方框移动与落子
switch (mes.vkcode)
{
case VK_UP:
if (movebox.ly >= INITIALY && !keyPressed)
{
movebox.ly -= BOXSIZE*2;
movebox.ry -= BOXSIZE*2;
keyPressed = true; // 记录按下状态
}
break;
case VK_DOWN:
if (movebox.ry <= 600 && !keyPressed)
{
movebox.ly += BOXSIZE*2;
movebox.ry += BOXSIZE*2;
keyPressed = true;
}
break;
case VK_LEFT:
if (movebox.lx >= INITIALX && !keyPressed)
{
movebox.lx -= BOXSIZE*2;
movebox.rx -= BOXSIZE*2;
keyPressed = true;
}
break;
case VK_RIGHT:
if (movebox.rx <= 600 && !keyPressed)
{
movebox.lx += BOXSIZE*2;
movebox.rx += BOXSIZE*2;
keyPressed = true;
}
break;
case VK_RETURN://落子
if (!keyPressed)
{
int x = (movebox.lx - BOXSIZE) / INITIALX;
int y = (movebox.ly - BOXSIZE) / INITIALX;
if (board[y][x] == 0)
{ // 检查是否已有棋子
board[y][x] = chess_type ? 1 : 2; // 1为白棋,2为黑棋
chess_type = !chess_type; // 切换棋子类型
}
keyPressed = true;
}
}
if (VectoryCheck(board) == 1)
{
MessageBox(NULL, "白方胜利!", "结束", MB_SYSTEMMODAL);
closegraph();
}
else if (VectoryCheck(board) == 2)
{
MessageBox(NULL, "黑方胜利!", "结束", MB_SYSTEMMODAL);
closegraph();
}
// 检测按键释放,重置状态
if (mes.message == WM_KEYUP)
{
keyPressed = false; // 释放按键时重置状态
}
}
return 0;
}