控制区的代码就不详细讲解了,自己看看就能明白,并没有什么难点。如果有兴趣的画还可以把界面做好一点,把功能做丰富一点。但是我觉得点到为止就行了。
我用的是Win32空项目写的,你也可以用自动生成的代码写,但是我觉得那个太乱代码太多。
接下来是全部的代码:
#include <windows.h>
#include <vector>
#include <time.h>
//游戏区和控制区宽度高度
#define GAME_WIDE 70 //游戏区宽度70*10
#define GAME_HEIGHT 60 //游戏区高度60*10
#define CTRL_WIDE 30 //控制区宽度30*10
#define CTRL_HEIGHT 60 //控制区高度60*10
#define BOUNDSIZE 10 //边界厚度10
//蛇的信息
#define SNAKESIZE 10 //蛇的宽高都为10的小方块
#define SNAKEMAX 100 //蛇身长度最大值为100
#define SNAKESPEED 500 //初始化蛇的速度为500ms
#define TIMERID_1 1 //定时器ID
#define PAUSEID 1 //暂停按钮ID
#define STARTID 2 //开始按钮ID
#define SPEEDUPID 3 //加速按钮ID
#define SPEEDDOWNID 4 //减速按钮ID
std::vector<POINT> vSnakePoint; //声明一个蛇的坐标
UCHAR ucSnakeHead = 4; //初始化蛇头为4,实际上是vSnakePoint[3];
UCHAR ucSnakeTail = 0; //初始化蛇尾为0
UCHAR ucSnakekLen = 4; //初始化蛇身长度为4
UINT uiSnakeSpeed = SNAKESPEED; //初始化蛇的速度为500ms
UINT uiScore = 0; //初始化分数为0
POINT ptDirectHead = { 1,0 }; //蛇头的方向,初始化为向右
POINT ptFood = { 20,20 }; //食物坐标
BOOL bIsLoose = TRUE; //判断是否输了
BOOL bIsWin = FALSE; //判断是否赢了
BOOL bIsPause = FALSE; //判断是否暂停了
BOOL bIsStart = FALSE; //判断是否开始了
BOOL bFlagStart = FALSE; //判断是否能开始
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
TCHAR szAppName[] = TEXT("Snake by _acme_");
HINSTANCE hInst = nullptr;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
hInst = hInstance;
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("This program requires Windows NT!"),
szAppName, MB_ICONERROR);
return 0;
}
hwnd = CreateWindow(szAppName, szAppName,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
//初始化蛇
VOID InitSnake()
{
//先清空坐标
vSnakePoint.clear();
//给蛇坐标分配空间
vSnakePoint.resize(SNAKEMAX);
//游戏结束后再次初始化
ucSnakeHead = 4;//初始化蛇头为4,实际上是vSnakePoint[3];
ucSnakeTail = 0;//初始化蛇尾为0
ucSnakekLen = 4;//初始化蛇身长度为4
uiSnakeSpeed = SNAKESPEED;//初始化蛇的速度为200ms
ptDirectHead = { 1,0 };//初始化蛇头方向
ptFood = { 20,20 };//初始化食物坐标
uiScore = 0;//初始化分数
//初始化每一节的蛇身坐标
for (int i = 0; i < ucSnakekLen; i++)
{
vSnakePoint[i].x = i;
vSnakePoint[i].y = 0;
}
}
//获取蛇结点位置
//index是一个偏移蛇尾的偏移量
POINT &GetSnakeNode(int index)
{
int i = ucSnakeTail + index;//蛇身的长度
if (i >= SNAKEMAX)
{
i -= SNAKEMAX;
}
return vSnakePoint[i];
}
//画蛇身
VOID DrawSnake(HDC hdc)
{
static HBRUSH hBrush = CreateSolidBrush(RGB(0, 243, 222));
SelectObject(hdc, hBrush);
//当前蛇结点
POINT ptCurrentNode = { 0 };
for (int i = 0; i < ucSnakekLen; i++)
{
ptCurrentNode = GetSnakeNode(i);
Rectangle(hdc, ptCurrentNode.x*SNAKESIZE + BOUNDSIZE, ptCurrentNode.y*SNAKESIZE + BOUNDSIZE,
(ptCurrentNode.x + 1)*SNAKESIZE + BOUNDSIZE, (ptCurrentNode.y + 1)*SNAKESIZE + BOUNDSIZE);
}
}
//设置食物坐标
void SetFood()
{
POINT ptNow = { 0 };
int x = 0, y = 0, i = 0;
while (true)
{
srand(time(0));//随机器种子
x = rand() % GAME_WIDE; //设置食物随机坐标
y = rand() % GAME_HEIGHT;
for (i = 0; i < ucSnakekLen; i++)
{
ptNow = GetSnakeNode(i);
if (ptNow.x == x && ptNow.y == y)
break;//如果食物出现在蛇身,则重新设置食物
}//如果食物不在蛇身,就退出循环
if (ucSnakekLen == i) break;
}
ptFood.x = x;
ptFood.y = y;
}
//判断蛇是否撞墙
BOOL IsStrikeWall(POINT ptHead)
{
if (ptHead.x < 0 || ptHead.x >= GAME_WIDE
|| ptHead.y < 0 || ptHead.y >= GAME_HEIGHT)
{
bIsLoose = FALSE;//撞墙输了
return FALSE;
}
return TRUE;
}
//判断是否撞到自己
BOOL IsStrikeSelf(POINT ptHead)
{
POINT ptNow = { 0 };
for (int i = 0; i < ucSnakekLen - 1; i++)
{
ptNow = GetSnakeNode(i);
if (ptHead.x == ptNow.x && ptHead.y == ptNow.y)
{
bIsLoose = FALSE;//撞自己输了
return FALSE;
}
}
return TRUE;
}
//判断是否吃到食物
BOOL IsEat(POINT ptHead)
{
return (ptHead.x == ptFood.x && ptHead.y == ptFood.y) ? TRUE : FALSE;
}
//显示分数
void ShowScore(HWND hwnd)
{
HDC hdc = GetDC(hwnd);
HPEN hPen = CreatePen(PS_SOLID, 4, RGB(255, 255, 0));
SelectObject(hdc, hPen);
int iLength = 0;
TCHAR szBuffer[20] = { 0 };
iLength = wsprintf(szBuffer, L"Score: %u", uiScore);
TextOut(hdc, BOUNDSIZE * 2 + GAME_WIDE*SNAKESIZE + 100,
BOUNDSIZE + 200, szBuffer, iLength);
ReleaseDC(hwnd, hdc);
DeleteObject(hPen);
}
//判断是否超出范围
void IsOverTake()
{
if (SNAKEMAX == ucSnakeHead) ucSnakeHead = 0; //超出范围往返循环
if (SNAKEMAX == ucSnakeTail) ucSnakeTail = 0;
if (SNAKEMAX == ucSnakekLen) bIsWin = TRUE;//赢了
}
//增加蛇的长度
void AddSnakeLen(POINT ptHead)
{
vSnakePoint[ucSnakeHead++] = ptHead;//蛇头增长
ucSnakekLen++;//长度+1
IsOverTake();//判断是否超出范围
}
//让蛇动起来
BOOL SnakeMove()
{
BOOL bRet = FALSE;
do
{
POINT ptNewNode = { 0 };//新的蛇头
ptNewNode.x = GetSnakeNode(ucSnakekLen - 1).x + ptDirectHead.x;
ptNewNode.y = GetSnakeNode(ucSnakekLen - 1).y + ptDirectHead.y;
if (!IsStrikeWall(ptNewNode))//判断新的蛇头是否撞墙
break;
if (!IsStrikeSelf(ptNewNode))//判断蛇是否撞到自己
break;
if (IsEat(ptNewNode))//判断是否吃到食物
{
SetFood();//重新设置食物坐标
uiScore += 10;//吃到一个食物加10分
AddSnakeLen(ptNewNode);//增加蛇的长度
break;
}
vSnakePoint[ucSnakeHead].x = ptNewNode.x;//蛇头移动
vSnakePoint[ucSnakeHead++].y = ptNewNode.y;
ucSnakeTail++;
IsOverTake();
bRet = TRUE;
} while (FALSE);
return bRet;
}
//画食物
void DrawFood(HWND hwnd)
{
HDC hdc = GetDC(hwnd);
HBRUSH hBrush = CreateSolidBrush(RGB(255, 0, 0));
SelectObject(hdc, hBrush);
//椭圆食物
Ellipse(hdc, BOUNDSIZE + ptFood.x*SNAKESIZE, BOUNDSIZE + ptFood.y*SNAKESIZE
, BOUNDSIZE + (ptFood.x + 1)*SNAKESIZE, BOUNDSIZE + (ptFood.y + 1)*SNAKESIZE);
ReleaseDC(hwnd, hdc);
DeleteObject(hBrush);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
RECT rect = { 0 };
static int ixWind = 0, iyWind = 0;//窗口大小参数
static int ixClient = 0, iyClient = 0;//客户区大小参数
static HWND hPause = nullptr;//暂停按钮句柄
static HWND hStart = nullptr;//开始按钮句柄
static HWND hSpeedUp = nullptr;//加速按钮句柄
static HWND hSpeedDown = nullptr;//减速按钮句柄
DWORD dwError = 0;//错误信息
switch (message)
{
case WM_CREATE:
{
//获取窗口大小
GetWindowRect(hwnd, &rect);
ixWind = rect.right - rect.left;
iyWind = rect.bottom - rect.top;
//获取客户区大小
GetClientRect(hwnd, &rect);
ixClient = rect.right - rect.left;
iyClient = rect.bottom - rect.top;
//修改窗口大小
int nWideth = BOUNDSIZE * 3 + (GAME_WIDE + CTRL_WIDE)*SNAKESIZE + (ixWind - ixClient);
int nHeight = BOUNDSIZE * 2 + GAME_HEIGHT*SNAKESIZE + (iyWind - iyClient);
MoveWindow(hwnd, 50, 50, nWideth, nHeight, TRUE);
//初始化蛇身
InitSnake();
//创建一个暂停按钮,宽100高50
hPause = CreateWindowW(L"button", L"Pause", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
BOUNDSIZE * 2 + GAME_WIDE*SNAKESIZE + 30, BOUNDSIZE + 20,
100, 50, hwnd, (HMENU)PAUSEID, hInst, 0);
//创建一个开始按钮,宽100高50
hStart = CreateWindowW(L"button", L"Start", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
BOUNDSIZE * 2 + GAME_WIDE*SNAKESIZE + 170, BOUNDSIZE + 20,
100, 50, hwnd, (HMENU)STARTID, hInst, 0);
//创建一个加速按钮,宽100高50
hSpeedUp = CreateWindowW(L"button", L"SpeedUp", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
BOUNDSIZE * 2 + GAME_WIDE*SNAKESIZE + 30, BOUNDSIZE + 90,
100, 50, hwnd, (HMENU)SPEEDUPID, hInst, 0);
//创建一个减速按钮,宽100高50
hSpeedDown = CreateWindowW(L"button", L"SpeedDown", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
BOUNDSIZE * 2 + GAME_WIDE*SNAKESIZE + 170, BOUNDSIZE + 90,
100, 50, hwnd, (HMENU)SPEEDDOWNID, hInst, 0);
}
break;
case WM_TIMER:
switch (wParam)
{
case TIMERID_1:
{ //移动蛇
SnakeMove();
//判断是否输了
if (FALSE == bIsLoose)
{
KillTimer(hwnd, TIMERID_1);
MessageBox(hwnd, L"you loose!", L"", MB_OK);
InitSnake();//初始化蛇
bIsLoose = TRUE;
SetWindowTextW(hStart, L"Start"); //如果输了就把ReStart变为Start
bIsStart = FALSE;//重置开始
bFlagStart = FALSE;
}
if (TRUE == bIsWin)//判断是否赢了
{
KillTimer(hwnd, TIMERID_1);
MessageBox(hwnd, L"you Win!", L"", MB_OK);
InitSnake();//初始化蛇
bIsWin = FALSE;
}
//重新画蛇
RECT rect = { 0 };
SetRect(&rect, 0, 0, BOUNDSIZE + GAME_WIDE*SNAKESIZE,
BOUNDSIZE + GAME_HEIGHT*SNAKESIZE);
InvalidateRect(hwnd, &rect, TRUE);
}
break;
default:
break;
}
break;
case WM_KEYDOWN://控制方向
if (FALSE == bIsLoose || TRUE == bIsPause)
return 0;
switch (wParam)
{
case VK_UP:
if (ptDirectHead.x != 0 && ptDirectHead.y != 1)
{
ptDirectHead.x = 0;
ptDirectHead.y = -1;
}
break;
case VK_DOWN:
if (ptDirectHead.x != 0 && ptDirectHead.y != -1)
{
ptDirectHead.x = 0;
ptDirectHead.y = 1;
}
break;
case VK_LEFT:
if (ptDirectHead.y != 0 && ptDirectHead.x != 1)
{
ptDirectHead.x = -1;
ptDirectHead.y = 0;
}
break;
case VK_RIGHT:
if (ptDirectHead.y != 0 && ptDirectHead.x != -1)
{
ptDirectHead.x = 1;
ptDirectHead.y = 0;
}
break;
default:
break;
}
break;
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// 分析菜单选择:
switch (wmId)
{
case SPEEDUPID://速度加
if (uiSnakeSpeed >= 0 && bIsStart == TRUE)
{
if (100 >= uiSnakeSpeed)
uiSnakeSpeed -= 10;
else
uiSnakeSpeed -= 100;
SetTimer(hwnd, TIMERID_1, uiSnakeSpeed, nullptr);//重新设置
}
SendMessage(hwnd, WM_SETFOCUS, 0, 0);
break;
case SPEEDDOWNID://速度减
{
if (uiSnakeSpeed <= 1000 && bIsStart == TRUE)
{
if (100 > uiSnakeSpeed)
uiSnakeSpeed += 10;
else
uiSnakeSpeed += 100;
SetTimer(hwnd, TIMERID_1, uiSnakeSpeed, nullptr);//重新设置
}
SendMessage(hwnd, WM_SETFOCUS, 0, 0);
}
break;
case STARTID://开始
{
if (FALSE == bIsStart && FALSE == bFlagStart)
{
SetTimer(hwnd, TIMERID_1, uiSnakeSpeed, nullptr);//设置定时器
bIsStart = TRUE;//设置为已经开始
bFlagStart = TRUE;//第一次开始后就设为TREU 表示不再进入这个函数
SetWindowTextW(hStart, L"ReStart");
SendMessage(hwnd, WM_SETFOCUS, 0, 0);//把输入焦点还给游戏区
}
else
{
if (TRUE == bIsPause)
{//如果已经暂停了,按下重新开始要记得把暂停按钮恢复原样
if (!SetWindowTextW(hPause, L"Pause"))
dwError = GetLastError();
bIsPause = FALSE;
}
InitSnake();//初始化,重新开始
SetTimer(hwnd, TIMERID_1, uiSnakeSpeed, nullptr);
SendMessage(hwnd, WM_SETFOCUS, 0, 0);//把焦点还给游戏区
}
}
break;
case PAUSEID://暂停
if (TRUE == bFlagStart)//游戏进行的时候才能暂停
{
if (FALSE == bIsPause)//如果没有暂停则将它暂停
{
KillTimer(hwnd, TIMERID_1);//关掉定时器就是暂停了
bIsPause = TRUE; //把状态设置为暂停
if (!SetWindowText(hPause, L"Go on"))//更改按钮标题
dwError = GetLastError();
}
else
{//按下继续按钮就重新创建定时器
SetTimer(hwnd, TIMERID_1, uiSnakeSpeed, nullptr);
bIsPause = FALSE;//把状态设置为没有被暂停
if (!SetWindowText(hPause, L"Pause"))//更改按钮标题
dwError = GetLastError();
SendMessage(hwnd, WM_SETFOCUS, 0, 0);//把焦点还给游戏区
}
}
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
}
break;
case WM_SETFOCUS:
SetFocus(hwnd);
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
static HBRUSH hBrush1 = CreateSolidBrush(RGB(58, 206, 66));
static HBRUSH hBrush2 = CreateSolidBrush(RGB(80, 87, 196));
//画游戏区
Rectangle(hdc, BOUNDSIZE, BOUNDSIZE, BOUNDSIZE + GAME_WIDE*SNAKESIZE,
BOUNDSIZE + GAME_HEIGHT*SNAKESIZE);
//填充游戏区颜色
SetRect(&rect, BOUNDSIZE, BOUNDSIZE, BOUNDSIZE + GAME_WIDE*SNAKESIZE,
BOUNDSIZE + GAME_HEIGHT*SNAKESIZE);
FillRect(hdc, &rect, hBrush1);
//画控制区
Rectangle(hdc, BOUNDSIZE * 2 + GAME_WIDE*SNAKESIZE, BOUNDSIZE,
BOUNDSIZE * 2 + (GAME_WIDE + CTRL_WIDE)*SNAKESIZE, BOUNDSIZE + CTRL_HEIGHT*SNAKESIZE);
//填充控制区颜色
SetRect(&rect, BOUNDSIZE * 2 + GAME_WIDE*SNAKESIZE, BOUNDSIZE,
BOUNDSIZE * 2 + (GAME_WIDE + CTRL_WIDE)*SNAKESIZE,
BOUNDSIZE + CTRL_HEIGHT*SNAKESIZE);
FillRect(hdc, &rect, hBrush2);
//画蛇身
DrawSnake(hdc);
//画食物
DrawFood(hwnd);
//显示分数
ShowScore(hwnd);
EndPaint(hwnd, &ps);
}
break;
case WM_DESTROY:
KillTimer(hwnd, TIMERID_1);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return DefWindowProc(hwnd, message, wParam, lParam);
}