C++实战小项目贪吃蛇网游化框架搭建1

前言

身边一些C++初学者看完理论后觉得没有什么项目经验,学习到的知识没地儿练手不说,在找工作时也很受阻,于是便拿出来这个自己刚学习时写的小项目,很适合初学者练手。
整个项目是一个网络游戏框架,能把学到的TCP/IP通信、MySQL数据库以及c++类的概念和多线程理念应用进去,以此复习同时也总结一下所有的知识,因此诞生了此系列文章。

游戏主题构架主要参考http://blog.csdn.net/u010505527/article/details/17227695,本着巩固以及提升自己写代码和调试的能力,因此本代码是在阅读该篇文章的基础上全部重新手敲,主体架构相同但细节几乎完全不同,并加入了自己的一些想法如按住方向键暂时加速、回车暂停、每得10分提升难度以及分数记录等等部分,当然,还有后面进行网游化的通信代码部分。

一、MAIN函数的主体构成

贪吃蛇这个游戏基本上大部分人都玩过,具体就不多提了,游戏架构设计部分完全基于C++面向对象的思想,将问题逐步分解,首先确定main函数的运行过程,再根据需求以此设计需要用到的类,当然,最初的MAIN函数并不完全,只确定了主体逻辑,之后进行了完善:

#include "Game.h"

void main()
{
	WSADATA wsaData;
	WORD sockVersion = MAKEWORD(2, 2);
	if (WSAStartup(sockVersion, &wsaData) != 0)
	{
		exit(0);
	}

	CONSOLE_CURSOR_INFO mycursor;
	GetConsoleCursorInfo(hout, &mycursor);
	mycursor.bVisible = false;
	SetConsoleCursorInfo(hout, &mycursor);

	Game mygame;
	mygame.newgame();
	mygame.gconnect();
	mygame.login();
	mygame.loaddata();
	system("cls");

	while (true) {
		mygame.paintwindow();
		mygame.set();
		mygame.playgame();
		if (!mygame.playagain()) 
			break;
		system("cls");
		mygame.reset();
	};
}

前面几行代码是sockets在windows下编程的初始化过程,之后将光标设置为不可见,新建一个game类,先完成绘制logo、连接服务器、登陆、载入服务器数据等一次化工作,之后进入一个循环,绘制游戏边界、设置、进入game运行的核心函数playgame,若游戏死亡则进入playagain函数询问是否再来一局,是则清屏并重置游戏,否则结束游戏。
###二、GAME类的设计
  根据main函数的需求来设计GAME类,可以确定基本需要的数据和接口以及相应的功能,依次完成即可。

#pragma once
#include "TCPhead.h"
#include "Food.h"
#include "Snake.h"
#include <fstream>
#include <string>
#define NDEBUG
using namespace std;
class  Game
{
public:
	Game();
	~Game();
	void newgame();
	void gconnect();
	void login();
	void loaddata();
	void paintwindow();
	void set();
	void playgame();
	bool playagain();
	void reset();
	void accelerate()
	{
		speed -= speed / 3;
		level++;
	}
	bool eaten()
	{
		return (snake.Head() == food.foodposition());
	}
	bool hit()
	{
		//撞墙
		int x, y;
		x = snake.Head().xx();
		y = snake.Head().yy();
		if (x == 2 || x == 100)
			return true;
		if (y == 3 || y == 30)
			return true;
		//撞自己
		return snake.hititself();
	}
	//状态栏
	void showstate()
	{
		SetOutputposition(2, 2, hout);
		cout << "Difficulty: " << level;
		SetOutputposition(48, 2, hout);
		cout << "Scores: " << score;
		SetOutputposition(88, 2, hout);
		cout << "Record: " << record;
	}
	bool senddata();
	
private:
	int speed;
	short level;
	Snake snake;
	Food food;
	unsigned short score;
	unsigned short record;
	int sockfd;	 
};

常用函数定义为内联函数,节省系统资源,gconnect、login、loaddata和senddata这几个函数为通信用,暂时不做解释,来看看游戏运行最主要的几个函数:
####1、newgame()、paintwindows()

void Game::newgame() {
	//SNAKES LOGO
	SetConsoleTextAttribute(hout, FOREGROUND_GREEN | FOREGROUND_INTENSITY);
	SetOutputposition(34, 6, hout);
	cout << " ■■■   ■     ■  ■■■  ■    ■ ■■■■";
	SetConsoleTextAttribute(hout, FOREGROUND_GREEN);
	SetOutputposition(34, 7, hout);
	cout << "■    ■  ■■   ■ ■    ■ ■   ■  ■";
	SetOutputposition(34, 8, hout);
	SetConsoleTextAttribute(hout, FOREGROUND_RED | FOREGROUND_INTENSITY);
	cout << "■        ■ ■  ■ ■    ■ ■  ■   ■";
	SetOutputposition(34, 9, hout);
	SetConsoleTextAttribute(hout, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
	cout << " ■■■   ■  ■ ■ ■■■■ ■■     ■■■";
	SetOutputposition(34, 10, hout);
	SetConsoleTextAttribute(hout, FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY);
	cout << "      ■  ■   ■■ ■    ■ ■  ■   ■";
	SetOutputposition(34, 11, hout);
	SetConsoleTextAttribute(hout, FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY);
	cout << "■    ■  ■     ■ ■    ■ ■   ■  ■";
	SetOutputposition(34, 12, hout);
	SetConsoleTextAttribute(hout, FOREGROUND_BLUE | FOREGROUND_INTENSITY);
	cout << " ■■■   ■     ■ ■    ■ ■    ■ ■■■■";
	int count = 6;
	int interval = 600;
	while (true) {
		//闪动字
		SetOutputposition(45, 24, hout);
		cout << "press any key to start...";
		Sleep(interval);
		for (int i = 0; i != 25; i++)
			cout << "\b \b";
		if (_kbhit()) {
			if (count-- == 0)
				break;
			interval = 50;
		}
		Sleep(interval);
		if (_kbhit()) {
			if (count-- == 0)
				break;
			interval = 50;
		}
	}
	while (_kbhit()) //清除输入流中的数据
		_getch();
}

newgame负责游戏SNAKES logo的绘制和闪动字的输出,值得注意的是最后用循环清除输入流中的数据,避免缓存中的数据对之后的部分产生影响。paintwindows()不多做解释,用于之后边框的绘制,与newgame中绘制snakes类似,setoutputposition函数在后文中解释。
####2、set()、reset()

void Game::set() {
	SetOutputposition(20, 20, hout);
	cout << "please choose difficulty:";
	SetOutputposition(50, 21, hout);
	cout << "1:easy" << endl;
	SetOutputposition(50, 22, hout);
	cout << "2:normal" << endl;
	SetOutputposition(50, 23, hout);
	cout << "3:hard";
choose:
	char k = _getch();
	switch (k)
	{
	case '1':
		level = 1;
		speed = 300;
		break;
	case '2':
		level = 2;
		speed = 200;
		break;
	case '3':
		level = 3;
		speed = 134;
		break;
	default:
		goto choose;
	}
	SetOutputposition(45, 20, hout);
	for (int i = 0; i != 25; i++)
		cout << "\b \b";
	SetOutputposition(60, 21, hout);
	for (int i = 0; i != 10; i++)
		cout << "\b \b";
	SetOutputposition(60, 22, hout);
	for (int i = 0; i != 10; i++)
		cout << "\b \b";
	SetOutputposition(60, 23, hout);
	for (int i = 0; i != 10; i++)
		cout << "\b \b";
}
void Game::reset()
{
	snake.reset();
	food.reset();
	score = 0;
}

set()主要用于初始难度选择,其实之后加入难度升级机制后这个部分可以略去,reset()用于每次重新游戏时的数据的重置。
####3、playgame()、playagain()

void Game::playgame() {
	food.foodpaint(hout);      //绘制食物  
	snake.paintbody(hout);     //绘制蛇身
	showstate();
	int speedr(speed);
	while (true) {
		char k;
		speed = speedr;
		while (_kbhit()) {
			k = _getch();
			if (k == '\r')
				system("pause>>nul");
			if(snake.changedirection(k))
				break;
			if (snake.press(k)) {
				speed = 50;
			}
		}
		snake.update();         //更新蛇出现的位置
		if (hit())            //判断死亡
			break;
		if (!eaten()) {//判定吃食物
			snake.tailclear(hout);
			snake.painthead(hout);
		}
		else
		{
			score++;
			if ((score / 10 - level) > 0) {
				accelerate();
				speedr = speed;
			}
			showstate();
			food.update();
			while (snake.overlap(food.foodposition()))//食物与蛇身重合
				food.update();
			food.foodpaint(hout);
		}
		senddata();
		Sleep(speed);
	}
}

bool Game::playagain()
{
	if (score > record) {
		record = score;
		SetOutputposition(45, 14, hout);
		cout << "NEW RECORD!!!" << endl;
	}
	SetOutputposition(45, 17, hout);
	cout << "GAME OVER!" << endl;
	SetOutputposition(45, 18, hout);
	cout << "play again ? Y or N" << endl;
	while (1) {
		char k = _getch();
		if (k == 'Y' || k == 'y')
			return true;
		else if (k == 'N' || k == 'n')
			return false;
	}
}

playgame()这个函数是这个类的最主要组成部分,整体逻辑结构如下图,只要判定没有撞墙或者撞自己,则置于一个永恒的循环中,同时包含判定是否吃到食物,是否按下方向键,是否转换方向等的处理代码。playagain()则比较简单,不多做赘述。

Created with Raphaël 2.2.0 开始 绘制食物、蛇 及状态栏 恢复原速 是否有输入? 判定输入为回车或者方向键 或按住并进行相应处理 更新蛇身数据 是否已撞死? 结束 是否吃到食物? 加分、更新并绘制食物位置 并判定是否加速 绘制更新蛇身 yes no yes no yes no

####4、其他函数
  主要用于判定吃食物、撞墙或撞自己以及状态栏显示,代码比较简单,不多说。
###三、Food类的设计
  从Game类中对Food类和Snake类的需求来创建这两个类,首先是Food类:

#pragma once
#include "Point.h"

class Food
{
public:
	Food(int a = 14, int b = 15) :food{ a,b } {}
	void foodpaint(HANDLE hand)
	{
		food.paint(hand);
	}
	const Point& foodposition()
	{
		return food;
	}

	void update()
	{
		food.xx() = 4+rand() % 48*2;
		food.yy() =  4 + rand() % 26;
	}
	bool sendfood(const int &sock);

	bool recvdata(int &sock);
	void reset()
	{
		food = { 14,15 };
	}
private:
	Point food;
};

这个类整体结构比较简单,主要是完成它的绘制、刷新和位置返回的接口,刷新部分用到随机数生成函数,这个部分一般需要定义一个起点,这里没有做,只是简单调用会导致每次游戏食物依次出现的位置次序是重复的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值