贪吃蛇-C++版

前面写了贪吃蛇的C语言版本,现在给出C++版本。
两个版本的共同之处:
1、均使用了不带头结点单链表作为蛇的载体
2、食物均采用rand()进行位置设定
不同之处:
1、C语言版本单独建立了一个线程,用来处理用户的输入,C++版本始终是一个线程
2、蛇的移动、更新食物均需要更新界面,C语言采取system(“cls”);实现,C++采用设置光标位置来设置蛇的移动,显得更加流畅

========================================================
本篇涉及到的知识点:
1、多文件编程(类内成员的外部实现)
2、处理键盘输入_kbhit();(如果没有键盘输入则该函数返回0)
3、获取光标的操作(这个代码好像不需要记,随拿随用)

void getoxy1(HANDLE hOut1, int x, int y){
	COORD pos;
	pos.X = x;
	pos.Y = y;
	SetConsoleCursorPosition(hOut1, pos);//
}
HANDLE hOut1 = GetStdHandle(STD_OUTPUT_HANDLE);//定义显示器句柄变量

4、某一个类的对象作为另一个类的成员变量

class A {//类A
private:
	B b;//设类B已经定义,此时B的一个对象b作为类A的成员变量
}

5、单链表的操作

  • 销毁、删除尾节点、头插法

==========================================================

游戏设计思路:
class Wall作为整个游戏的基础,其他类通过操作Wall内部的gameArray数组来实现蛇或食物的显现。
一张图显示三个类(Wall、Food、Snake)之间的关系
在这里插入图片描述

=========================================================
class Wall(wall.h wall,.cpp)–负责整体游戏的显现(路径、食物、蛇)
wall.h

#ifndef _WALL_HEAD_
#define _WALL_HEAD_
#include <iostream>
using namespace std;

class Wall{
public:
	enum{
		ROW = 26,
		COL = 26
	};
	//初始化墙壁
	void initWall();
	//画墙壁
	void drawWall();
	//根据索引设置二维数的内容
	void setWall(int x, int y, char c);
	//根据索引获取当前位置的符号
	char getWall(int x, int y);

private:
	char gameArray[ROW][COL];
};
#endif // !_WALL_HEAD_
;

wall.cpp

#include "wall.h"

void Wall::initWall(){
	for (int i = 0; i < ROW; ++i){
		for (int j = 0; j < COL; ++j){
			if (i == 0 || j == 0 || i == ROW - 1 || j == COL - 1)
				gameArray[i][j] = '*';
			else
				gameArray[i][j] = ' ';
		}
	}
}
void Wall::drawWall(){
	for (int i = 0; i < ROW; ++i){
		for (int j = 0; j < COL; ++j){
			cout << gameArray[i][j]<<" ";
		}
		if (i == 5)
			cout << "\t制作人:yhz";
		if (i == 6)
			cout << "\tW 向上";
		if (i == 7)
			cout << "\tS 向下";
		if (i == 8)
			cout << "\tA 向左";
		if (i == 9)
			cout << "\tD 向右";
		cout << endl;
	}
}
void Wall::setWall(int x, int y, char c){
	gameArray[x][y] = c;
}
char Wall::getWall(int x, int y){
	return gameArray[x][y];
}

class Snake(snake.h snake.cpp)–蛇节点的删除、增加、移动(吃食与不吃食)
snake.h

#pragma once
#include <iostream>
#include "wall.h"
#include "food.h"
using namespace std;

class Snake{
public:
	enum{
		UP = 'w',
		DOWN = 's',
		LEFT = 'a',
		RIGHT = 'd'
	};

	Snake(Wall& tempWall,Food& tempFood);
	//节点
	struct Point 
	{
		int x, y;
		Point* next;
	};

	//初始化蛇
	void initSnake();
	//销毁节点
	void destoryPoint();
	//添加节点
	void addPoint(int x,int y);
	//删除节点(尾节点)
	void delPoint();

	//蛇移动(返回值判断是否移动成功) key表示移动按键
	bool move(char key);

	/************设定难度************/
	//获取刷屏时间
	int getSleepTime();
	//获取蛇的长度
	int countList();
	//得分
	int getScore();

private:
	Point* pHead;	//蛇头
	Wall& wall;		//蛇需要绘制在数组中
	Food& food;		//蛇与食物的位置判断
	bool isRool;	//蛇自己转圈
};

snake.cpp

  • 蛇头的下一个位置如果是现在蛇尾的位置,则蛇移动后不应该死亡,允许蛇转圈(这个功能有isRool变量来控制)
#include "snake.h"
#include <windows.h>

void getoxy1(HANDLE hOut1, int x, int y){
	COORD pos;
	pos.X = x;
	pos.Y = y;
	SetConsoleCursorPosition(hOut1, pos);//
}
HANDLE hOut1 = GetStdHandle(STD_OUTPUT_HANDLE);//定义显示器句柄变量



Snake::Snake(Wall& tempWall, Food& tempFood) :wall(tempWall),food(tempFood){
	pHead = NULL;
	isRool = false;//蛇默认不转圈
}

//初始化蛇
void Snake::initSnake(){
	destoryPoint();		//先将原来的蛇销毁
	addPoint(5, 3);
	addPoint(5, 4);
	addPoint(5, 5);
}
//销毁所有节点
void Snake::destoryPoint(){
	Point* pCur = pHead;
	while (pHead){
		pCur = pHead->next;
		wall.setWall(pHead->x, pHead->y, ' ');//删除前修改蛇所在位置的标志
		//TODO...
		delete pHead;
		pHead = pCur;
	}
}
//添加节点
void Snake::addPoint(int x, int y){
	Point* newPoint = new Point;
	newPoint->x = x;
	newPoint->y = y;
	newPoint->next = NULL;

	//如何蛇头不空,则更改蛇头为蛇身
	if (pHead){
		wall.setWall(pHead->x, pHead->y, '=');
		getoxy1(hOut1, pHead->y*2, pHead->x);
		cout << "=";
	}
	newPoint->next = pHead;
	pHead = newPoint;			//更新蛇头的位置
	wall.setWall(pHead->x, pHead->y, '@');//设置蛇头的标志@
	getoxy1(hOut1, pHead->y*2, pHead->x);
	cout << "@";
}

void Snake::delPoint(){
	//两个节点以上才删除
	if (pHead == NULL || pHead->next == NULL)
		return;
	Point* pPre = pHead;
	Point* pCur = pHead->next;
	while (pCur->next){
		pCur = pCur->next;
		pPre = pPre->next;
	}
	//删除为节点
	wall.setWall(pCur->x, pCur->y, ' ');
	getoxy1(hOut1, pCur->y*2, pCur->x);
	cout << " ";
	delete pCur;
	pCur = NULL;
	pPre->next = NULL;
}

//蛇移动
bool Snake::move(char key){
	//获取当前蛇头的位置
	int x = pHead->x;
	int y = pHead->y;
	switch (key){
	case UP:
		--x;
		break;
	case DOWN:
		++x;
		break;
	case LEFT:
		--y;
		break;
	case RIGHT:
		++y;
		break;
	default:
		break;
	}

	//如果蛇头的下一个位置是蛇尾,则蛇不应死亡,而是转圈
	//Point* pPre = pHead;
	Point* pCur = pHead->next;
	while (pCur->next){
		pCur = pCur->next;
		//pPre = pPre->next;
	}
	if (pCur->x == x&&pCur->y == y){
		isRool = true;//蛇在循环
	}
	else{
		//判断蛇头的目标位置是否可以去
		if (wall.getWall(x, y) == '*' || wall.getWall(x, y) == '='){
			//蛇的下一个位置是墙,则显示出蛇死的状态图
			addPoint(x, y);
			delPoint();
			system("cls");
			wall.drawWall();
			cout << "最终得分:" << getScore() << endl;
			cout << "game over!" << endl;
			return false;
		}
	}


	//移动成功:吃到食物  未吃到食物
	if (wall.getWall(x, y) == '#'){
		addPoint(x, y);
		//重设食物
		food.setFood();
	}
	else{//未吃到食物,前进
		addPoint(x, y);
		delPoint();
		if (isRool){//此时蛇头快要碰上蛇尾,即蛇头蛇尾将要在同一个位置这个位置需要重绘
			wall.setWall(x, y, '@');
			getoxy1(hOut1, 2*y,x);
			cout << "@";
		}
	}
	return true;
}
//获取刷屏时间
int Snake::getSleepTime(){
	int size = countList();
	int sleepTime = 0;
	if (size < 5)
		sleepTime = 300;
	else if (size>5 && size < 10)
		sleepTime = 200;
	else
		sleepTime = 100;
	return sleepTime;
}
//获取蛇的长度
int Snake::countList(){
	Point* pTemp = pHead;
	int size = 0;
	while (pTemp){
		pTemp = pTemp->next;
		++size;
	}
	return size;
}
//得分为蛇吃掉食物的个数
int Snake::getScore(){
	return countList() - 3;
}

class Food(food.h food.cpp)–随机获取食物的正确位置,将食物放置在gameArray中
food.h

#pragma once
#include "wall.h"

class Food{
public:
	Food(Wall& tempWall);
	void setFood();
private:
	int foodX;
	int foodY;
	Wall& wall;		//食物要绘制在二维数组中
};

food.cpp

#include "food.h"
#include<ctime>
#include <windows.h>


void getoxy2(HANDLE hOut2, int x, int y){
	COORD pos;
	pos.X = x;
	pos.Y = y;
	SetConsoleCursorPosition(hOut2, pos);//
}

HANDLE hOut2 = GetStdHandle(STD_OUTPUT_HANDLE);//定义显示器句柄变量

Food::Food(Wall& tempWall) :wall(tempWall){

}

void Food::setFood(){
	srand((unsigned)time(NULL));
	//while保证食物放置在正确的位置
	while (1){
		foodX = rand() % (Wall::ROW - 2) + 1;
		foodY = rand() % (Wall::COL - 2) + 1;
		//cout << foodX <<" "<< foodY << endl;
		//只有不是蛇身和蛇头的地方才可以生成食物
		if (wall.getWall(foodX, foodY) == ' '){
			wall.setWall(foodX, foodY, '#');
			getoxy2(hOut2, foodY * 2, foodX);
			cout << "#";
			break;
		}
	}
}

主函数main.cpp
这个文件中的主要篇幅是在处理用户的输入问题

  • 刚开始时蛇静止,蛇头向右,按下’a’蛇不动,不会死
  • 本次按键与蛇移动方向成180°角时,蛇继续沿着前面的方面前进,不会死亡
  • 只对wasd四个按键做出反应,其余按键不改变蛇的前进方向
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include "wall.h"
#include"snake.h"
#include "food.h"
#include <conio.h>
#include <windows.h>

void getoxy(HANDLE hOut, int x, int y){
	COORD pos;
	pos.X = x;
	pos.Y = y;
	SetConsoleCursorPosition(hOut,pos);//
}

HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);//定义显示器句柄变量

int main(){
	bool isDead = false;//初始假设蛇未死亡
	char preKey = NULL;//上一次的按键输入

	Wall wall;
	wall.initWall();
	wall.drawWall();

	Food food(wall);
	food.setFood();

	Snake snake(wall,food);
	snake.initSnake();

	getoxy(hOut, 0, Wall::ROW);
	cout << "当前得分:" << snake.getScore() << endl;
	while (!isDead){
		char key = _getch();
		//启动游戏时,蛇静止,按下左键‘a’蛇不动,也不会死,等待下一次有效按键
		if (preKey == NULL&&key == snake.LEFT)
			continue;
		do{
			//本次按键方向与上一次完全相反
			if ((key == snake.UP&&preKey == snake.DOWN) ||
				(key == snake.DOWN&&preKey == snake.UP) ||
				(key == snake.RIGHT&&preKey == snake.LEFT) ||
				(key == snake.LEFT&&preKey == snake.RIGHT)){
				key = preKey;
			}
			else{
				preKey = key;//按键与蛇前进方向不冲突  更新按键
			}
			//只有wasd时蛇才移动
			if (key == snake.UP || key == snake.DOWN || key == snake.LEFT || key == snake.RIGHT){
				if (snake.move(key)){
					//system("cls");
					//wall.drawWall();
					getoxy(hOut, 0, Wall::ROW);
					cout << "当前得分:" << snake.getScore() << endl;
					Sleep(snake.getSleepTime());
				}
				else{
					isDead = true;//蛇死亡
					break;		  //退出内层while
				}
			}
			else{//对wasd以外的按键不反应(不改变前进方向)
				key = preKey;//强制将wasd以外的按键更改为上次的移动按键
			}
			preKey = key;
			
		} while (!_kbhit());//当没有键盘输入的时候返回0
	}
	system("pause");
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章