在我们写这个小程序之前我们考虑以下的几点:
一、明确需求:
(1) 只有一条蛇
(2) 只有一个食物
(3) 游戏界面,只有边界和部分有墙
(4) game over : (1)撞墙 (2)撞自己
(5) 字符界面
(6) 每吃一个加分
二、MVC(model:数据结构、view:界面、 controller:控制流)
1. M:自顶向下分析
蛇:看作链表,用坐标表示
食物:坐标
墙(已知宽和高)
GAME
我们可以知道需要以下结构体:
(1) 坐标
(2) 链表
(3) 蛇(蛇头、方向)
(4) 方向
(5) 其他
#pragma once
//坐标原点在左上角
//x是向右 y是向下
typedef struct Position{
int x;
int y;
}Position;
//链表结点(单向)
typedef struct Node{
struct Node *pNext;
Position data;
}Node;
//方向 (枚举列举所有可能)
typedef enum Direction{
UP, DOWN, LEFT, RIGHT
}Direction;
//蛇的结构
typedef struct Snake{
Node *pHead;
Direction direction;
}Snake;
//游戏用到的其他非独立字段
typedef struct Game{
Snake snake;
Position foodPosition;
int score;
int sorePerfood;
int speed;
int width; // 宽
int height; // 高
}Game;
2. V :view
(1)显示墙
#pragma once
#include <windows.h>
#include <stdio.h>
#include "model.h"
void SetPos(int X, int Y)
{
HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);//获得句柄
COORD coord;//实现光标的移动
coord.X = X;
coord.Y = Y;
SetConsoleCursorPosition(hStdout, coord); //控制光标
}
//显示外墙
void DisplayWall(int width, int height)
{
int i;
//上边
SetPos(0,0);
for (i = 0; i < width + 2; i++)
{
printf("■");
}
//下边
SetPos(0,height + 1);
for (i = 0; i < width + 2; i++){
printf("■");
}
//左边
for (i = 0; i < height +2 ; i++){
SetPos(0, i);
printf("■");
}
//右边
for (i = 0; i < height + 2; i++){
SetPos(2*(width + 1), i); //由于用了中文字符,一个中文是二个字符
printf("■");
}
}
(2)显示蛇
//显示蛇的位置
void DisplaySnakeNode(Position pos)
{
SetPos(2 * (pos.x + 1), pos.y + 1); //用到了中文字符
printf("●");
}
//显示蛇
void DisplaySnake(const Snake *pSnake)
{
Node *pNode = pSnake->phead;
while (pNode)
{
DisplaySnakeNode(pNode->data);//显示蛇的位置
pNode = pNode->pNext;
}
}
(3)显示食物
//显示食物
void DisplayFood(Position pos)
{
SetPos(2 * (pos.x + 1), pos.y + 1); //用了中文字符
printf("★");
}
我对宽和高做了改动,所以界面比上面的大
(4)清除数据
//清除数据
void CleanSnakeNode(Position pos)
{
SetPos(2 * (pos.x + 1), pos.y + 1); //用了中文字符
printf(" ");
}
3.controller 控制流
游戏的一系列实现
(1)对游戏先初始化
//蛇的初始化
void SnakeInit(Snake *psnake)
{
//假定蛇开始长度为3,坐标分别为(5,5)、(6,5)、(7,5)
//(7,5)->(6,5)->(5,5)
//采用头插进行初始化
int i;
psnake->pHead= NULL;
for (i = 0; i < 3; i++)
{
Position pos;
pos.x = i + 5;
pos.y = 5;
Node *pNewNode = (Node *)malloc(sizeof(Node));
assert(pNewNode);
pNewNode->data = pos;
pNewNode->pNext = psnake->pHead;
psnake->pHead = pNewNode;
}
psnake->direction = RIGHT;
}
//1重叠 0不重叠
int IsOverlapSnake(int x, int y, const Snake *pSnake)
{
Node *pNode;
for (pNode = pSnake->pHead; pNode; pNode = pNode->pNext)
{
if (pNode->data.x == x && pNode->data.y == y)
{
return 1;
}
}
return 0;
}
//食物的初始化
void FoodInit(Position *pFood, int width, int height, const Snake *pSnake)
{
int x;
int y;
do{
x = rand() % width;
y = rand() % height;
} while (IsOverlapSnake(x, y, pSnake));
pFood->x = x;
pFood->y = y;
}
//game的初始化
void GameInit(Game *pGame)
{
pGame->height =32;
pGame->width =28;
system("mode con cols=60 lines=38");
pGame->score = 0;
pGame->speed = 200;
//pGame->scorePerFood= 10;
SnakeInit(&(pGame->snake));
//食物(1)随机生成 (2)不能出墙 (3)不能和蛇重合
FoodInit(&(pGame->foodPosition),
pGame->width, pGame->height,
&(pGame->snake));
}
(2)实现字符界面(墙、蛇、食物)
DisplayWall(game.width, game.height);
DisplaySnake(&(game.snake));
DisplayFood(game.foodPosition);
(3)如何走
1.判断蛇不可以180度走
//判断蛇不可以180度转向
if (GetAsyncKeyState(VK_UP) && game.snake.direction != DOWN)
{
game.snake.direction = UP;
}
if (GetAsyncKeyState(VK_DOWN) && game.snake.direction != UP)
{
game.snake.direction = DOWN;
}
if (GetAsyncKeyState(VK_LEFT) && game.snake.direction != RIGHT)
{
game.snake.direction = LEFT;
}
if (GetAsyncKeyState(VK_RIGHT) && game.snake.direction != LEFT)
{
game.snake.direction = RIGHT;
}
2.一个周期内可能做得事请
//一个周期内可能做得事情
//1 蛇前进 (1)没吃到食物 添加蛇头结点,删除蛇尾结点
// (2)吃到食物 添加蛇头结点,不删除蛇尾,在生成食物
// 判断下一个前进方向和食物有无重合
//2 游戏结束 (1)撞墙 (2)撞自己
Position NextPos = GetNextPosition(&(game.snake)); //得到下一个前进的位置
//进行判断
if (NextPos.x == game.foodPosition.x && NextPos.y == game.foodPosition.y)
{
//吃到食物
PushFront(&(game.snake),NextPos); //添加蛇头
game.score += game.sorePerfood; //得分
FoodInit(&(game.foodPosition), game.width, game.height, &(game.snake));//生成食物
DisplayFood(game.foodPosition);
}
else{
//没吃到食物
PushFront(&(game.snake), NextPos); //添加蛇头
PopBack(&(game.snake)); //删除蛇尾
}
if (IsCashWall(game.width, game.height, NextPos))
{
break;
}
if (IsCashhimself(&(game.snake)))
{
break;
}
Sleep(game.speed);
}
所有代码如下~
main.c
#include <windows.h>
#include "controller.h"
#include "view.h"
int main()
{
printf("欢迎\n");
system("pause");
system("cls");
printf(" 欢迎\n");
system("pause");
system("cls");
printf("热烈欢迎\n");
system("pause");
system("cls");
GameStart();
system("pause");
return 0;
}
view.h
#pragma once
#include <windows.h>
#include <stdio.h>
#include "model.h"
void SetPos(int X, int Y)
{
HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);//获得句柄
COORD coord;//实现光标的移动
coord.X = X;
coord.Y = Y;
SetConsoleCursorPosition(hStdout, coord); //控制光标
}
//显示外墙
void DisplayWall(int width, int height)
{
int i;
//上边
SetPos(0,0);
for (i = 0; i < width + 2; i++)
{
printf("■");
}
//下边
SetPos(0,height + 1);
for (i = 0; i < width + 2; i++){
printf("■");
}
//左边
for (i = 0; i < height +2 ; i++){
SetPos(0, i);
printf("■");
}
//右边
for (i = 0; i < height + 2; i++){
SetPos(2*(width + 1), i);
printf("■");
}
}
//显示蛇的位置
void DisplaySnakeNode(Position pos)
{
SetPos(2 * (pos.x + 1), pos.y + 1); //用到了中文字符
printf("●");
}
//显示蛇
void DisplaySnake(const Snake *pSnake)
{
Node *pNode = pSnake->pHead;
while (pNode)
{
DisplaySnakeNode(pNode->data);//显示蛇的位置
pNode = pNode->pNext;
}
}
//显示食物
void DisplayFood(Position pos)
{
SetPos(2 * (pos.x + 1), pos.y + 1); //用了中文字符
printf("★");
}
//清除数据
void CleanSnakeNode(Position pos)
{
SetPos(2 * (pos.x + 1), pos.y + 1); //用了中文字符
printf(" ");
}
model.h
#pragma once
//坐标原点在左上角
//x是向右 y是向下
typedef struct Position{
int x;
int y;
}Position;
//链表结点(单向)
typedef struct Node{
struct Node *pNext;
Position data;
}Node;
//方向 (枚举列举所有可能)
typedef enum Direction{
UP, DOWN, LEFT, RIGHT
}Direction;
//蛇的结构
typedef struct Snake{
Node *pHead;
Direction direction;
}Snake;
//游戏用到的其他非独立字段
typedef struct Game{
Snake snake;
Position foodPosition;
int score;
int sorePerfood;
int speed;
int width; // 宽
int height; // 高
}Game;
controller.h
#pragma once
#include "model.h"
#include "view.h"
#include <windows.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
//蛇的初始化
void SnakeInit(Snake *psnake)
{
//假定蛇开始长度为3,坐标分别为(5,5)、(6,5)、(7,5)
//(7,5)->(6,5)->(5,5)
//采用头插进行初始化
int i;
psnake->pHead= NULL;
for (i = 0; i < 3; i++)
{
Position pos;
pos.x = i + 5;
pos.y = 5;
Node *pNewNode = (Node *)malloc(sizeof(Node));
assert(pNewNode);
pNewNode->data = pos;
pNewNode->pNext = psnake->pHead;
psnake->pHead = pNewNode;
}
psnake->direction = RIGHT;
}
//1重叠 0不重叠
int IsOverlapSnake(int x, int y, const Snake *pSnake)
{
Node *pNode;
for (pNode = pSnake->pHead; pNode; pNode = pNode->pNext)
{
if (pNode->data.x == x && pNode->data.y == y)
{
return 1;
}
}
return 0;
}
//食物的初始化
void FoodInit(Position *pFood, int width, int height, const Snake *pSnake)
{
int x;
int y;
do{
x = rand() % width;
y = rand() % height;
} while (IsOverlapSnake(x, y, pSnake));
pFood->x = x;
pFood->y = y;
}
//game的初始化
void GameInit(Game *pGame)
{
pGame->height =32;
pGame->width =28;
system("mode con cols=60 lines=38");
pGame->score = 0;
pGame->speed = 200;
//pGame->scorePerFood= 10;
SnakeInit(&(pGame->snake));
//食物(1)随机生成 (2)不能出墙 (3)不能和蛇重合
FoodInit(&(pGame->foodPosition),
pGame->width, pGame->height,
&(pGame->snake));
}
//得到蛇的下一个前进方向
Position GetNextPosition(const Snake *pSnake)
{
Position nextPos;
nextPos.x = pSnake->pHead->data.x;
nextPos.y = pSnake->pHead->data.y;
switch (pSnake->direction)
{
case UP:
nextPos.y -= 1;
break;
case DOWN:
nextPos.y += 1;
break;
case LEFT:
nextPos.x -= 1;
break;
case RIGHT:
nextPos.x += 1;
break;
}
return nextPos;
}
//添加蛇头(头插)
void PushFront(Snake *pSnake, Position NextPos)
{
Node *pNewNode = (Node *)malloc(sizeof(Node));
assert(pNewNode);
pNewNode->data = NextPos;
pNewNode->pNext = pSnake->pHead;
pSnake->pHead= pNewNode;
DisplaySnakeNode(NextPos);
}
//删除蛇尾(尾删)
void PopBack(Snake *pSnake) //删除蛇尾
{
Node *pNode ;
for (pNode = pSnake->pHead; pNode->pNext->pNext != NULL; pNode = pNode->pNext)
{}
//找到了倒数第二个结点
CleanSnakeNode(pNode->pNext->data);//清楚掉倒数第一个
free(pNode->pNext);
pNode->pNext = NULL;
}
//撞墙 1撞了 0没有
int IsCashWall(int width, int height,Position NextPos)
{
if (NextPos.x <0 || NextPos.x >= width )
{
return 1;
}
if (NextPos.y < 0 || NextPos.y>= height)
{
return 1;
}
return 0;
}
//撞自己 1撞了 0没有
int IsCashhimself(const Snake *pNake)
{
Node *pHead = pNake->pHead;
Node *pNode = pNake->pHead->pNext;
while (pNode!=NULL)
{
if (pHead->data.x == pNode->data.x && pHead->data.y == pNode->data.y)
{
return 1;
}
pNode = pNode->pNext;
}
return 0;
}
void GameStart()
{
Game game;
GameInit(&game);
DisplayWall(game.width, game.height);
DisplaySnake(&(game.snake));
DisplayFood(game.foodPosition);
while (1)
{
//判断蛇不可以180度转向
if (GetAsyncKeyState(VK_UP) && game.snake.direction != DOWN)
{
game.snake.direction = UP;
}
if (GetAsyncKeyState(VK_DOWN) && game.snake.direction != UP)
{
game.snake.direction = DOWN;
}
if (GetAsyncKeyState(VK_LEFT) && game.snake.direction != RIGHT)
{
game.snake.direction = LEFT;
}
if (GetAsyncKeyState(VK_RIGHT) && game.snake.direction != LEFT)
{
game.snake.direction = RIGHT;
}
//一个周期内可能做得事情
//1 蛇前进 (1)没吃到食物 添加蛇头结点,删除蛇尾结点
// (2)吃到食物 添加蛇头结点,不删除蛇尾,在生成食物
// 判断下一个前进方向和食物有无重合
//2 游戏结束 (1)撞墙 (2)撞自己
Position NextPos = GetNextPosition(&(game.snake)); //得到下一个前进的位置
//进行判断
if (NextPos.x == game.foodPosition.x && NextPos.y == game.foodPosition.y)
{
//吃到食物
PushFront(&(game.snake),NextPos); //添加蛇头
game.score += game.sorePerfood; //得分
FoodInit(&(game.foodPosition), game.width, game.height, &(game.snake));//生成食物
DisplayFood(game.foodPosition);
}
else{
//没吃到食物
PushFront(&(game.snake), NextPos); //添加蛇头
PopBack(&(game.snake)); //删除蛇尾
}
if (IsCashWall(game.width, game.height, NextPos))
{
break;
}
if (IsCashhimself(&(game.snake)))
{
break;
}
Sleep(game.speed);
}
}
运行结果:
得分项还没有写出来,后续完善~