前面寫了貪喫蛇的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;
}