注:本程序为本人数据结构算法练习代码,不具有任何实用性。本程序在Turbo C下可以完美运行。
贪吃蛇作为一个即时性要求很高的游戏,对程序的时间复杂度要求很低。为了达到这一目的,牺牲了一定的空间复杂度和两个重要的算法作为代价达到了较低的时间复杂度。本程序中存在三个重要的算法,下边我简单的讲一讲代码。
typedef struct SNAKE_BODYS{
int bodyrow;
int bodycol;
}SNAKE_BODYS;
typedef struct SNAKE{
int headIndex; //蛇头位置信息在数组中的下标
int length; //蛇的长度
int curlength; //蛇的当前显示的长度
int maxlength; //蛇的最大长度
int direction; //蛇的方向
SNAKE_BODYS snake_bodys[MAX_LENGTH];//蛇所有位置信息
}SNAKE;
蛇的位置的存储作为一个比较麻烦的事情,要存储蛇身体的每个点坐标,而且每个点的信息都在改变这是一个极其耗费时间的操作。综合考虑多个方面,最后决定通过循环数组的方式来实现蛇身体坐标的储存。虽然蛇在屏幕上给人的感觉是不停的在移动,但是实际上他改变的只有蛇头、蛇颈、以及蛇尾。所以在数组中只需要给新的headIndex = (headIndex+1)%maxlength并将蛇头位置存储在sanke_bodys[headIndex]就可以形成循环数组。这个方法的便捷之处在于蛇的每次移动只改变snake_bodys[MAX_LENGTH]数组中的一个位置以及headIndex就可以完成。这很简单的完成了蛇的移动的操作。降低了时间复杂度。
short pointStata[2000];
Turbo C作为一个早期的集成环境,它屏幕上是一个可以显示80*25字节信息即:2000个字节。所以我用一个长度为2000的short类型的数组在储存每一个字节的状态(通过对应下标的转化就可以转化的每一个下标)。例如:用0表示改地方没有显示任何东西、用1表示该地方有食物、用30表示该地方有障碍、用11表示该地方有蛇的身体等等。通过这个方法就可以表示出屏幕上每一个点的状态。这个面的状态要随时更新,这个更新极少,只有在产生新食物的时候更新食物位置对应下标的状态和蛇向前移动一步的时更新之前蛇尾的位置和现在蛇头的位置。但是在更新蛇头位置需要判断一下舌头将要去的位置是否有障碍、食物、其他蛇等等需要判断状态、这时候这个数组的存在就对于这些的判断显极其简单。这时候只需要转化到对应下标,取得对应下标的状态进行判断。这里节省了时间。降低了时间复杂度。
typedef struct ONEFOOD{
int index;
}ONEFOOD;
typedef struct FOOD{
ONEFOOD onefood[FOODNUM];
int foodNum;
}FOOD;
void prodectFood(FOOD *food) {
int validspace = 0;
short space[2000];
int i;
int random;
int foodindex;
int tmp;
for(i = 0;i<2000;i++) {
if(pointStata[i] == 0) {
space[validspace] = i;
validspace++;
}
}
for(i = 0;i<FOODNUM;i++) {
random = rand()%validspace;
foodindex = space[random];
(*food).onefood[i].index = foodindex;
pointStata[foodindex] = HAVA_ONEFOOD;
tmp = space[foodindex];
space[foodindex] = space[validspace-1];
space[validspace-1] = tmp;
(*food).foodNum++;
validspace--;
}
showfoods(food);
}
先解释一下上面代码定义的结构的,ONEFOOD里面储存某一食物的出现位置的转化为一维数组的下标。FOOD存储多个食物以及现在屏幕上的食物个位。当食物数量等于0的时候会调用prodectFood(FOOD *food)在屏幕上新输出5个食物。
由于在产生食物时候需要与之前的已经产生的食物、屏幕上的障碍、以及蛇的身体查重。这个过程是极其耗费时间的一个过程,在之前我通过每次查的方法实现,在每次产生新的食物的时候存在一个很明显的卡顿。产生这个问题的鱼那样是由于多次查重浪费了大量的时间,所以查重是这个问题的罪魁祸首。因此我想到了著名的洗牌算法可以完美的解决这个问题。
在我的程序中产生随机食物的时候是这样完成的:1、先申请一个short类型2000长度的数组short space[2000];2、通过之前申请的pointStata 这个数组,遍历这个数组得到每个点的状态。得到为空的下标存入space这个空间,并且得到为空的个个数validspace。3、通过rand()%validspace得到一个随机的下标tmpIndex,查看对应下标的值space[tmpIndex]。得到第一个食物的下标。4、将得到的下标存起来,然后交换space[validspace]的值和space[tmpIndex]的值,再将validspace减一。然后重复3.4步骤得到其他的食物下标。
上面的这种思路避免了多次查重,极大的降低了时间复杂度。
注:在以上代码中屏幕对应下标是从0开始的。之间的转化关系如下:
index = (col-1)*80+row-1;
//index为屏幕位置对应的下标;col为纵坐标的值,row为横坐标的值。80位一行能够输出的字节的个数。
col = (int)(index/80)+1;
row = index%80+1;
附录:程序源代码
#include <dos.h>
#include <stdio.h>
#include <time.h>
#include <bios.h>
#include <conio.h>
#define KEY_ESC 0x011b
#define KEY_RIGHT 0x4d00
#define KEY_LEFT 0x4b00
#define KEY_UP 0x4800
#define KEY_DOWN 0x5000
#define KEY_PGUP 0x4900
#define KEY_PUDN 0x5100
#define KEY_ENTER 0x1c0d
#define OVERINDEX 0
#define NO_OVERINDEX 1
#define TIME 1000000
#define FOODNUM 5
#define MAX_LENGTH 100
#define HAVA_OBSTACLE 30
#define HAVA_ONEFOOD 1
typedef struct DELTA_MOVE{
int deltarow;
int deltacol;
}DELTA_MOVE;
typedef struct SNAKE_BODYS{
int bodyrow;
int bodycol;
}SNAKE_BODYS;
typedef struct SNAKE{
int headIndex;
int length;
int curlength;
int maxlength;
int direction;
SNAKE_BODYS snake_bodys[MAX_LENGTH];
}SNAKE;
typedef struct ONEFOOD{
int index;
}ONEFOOD;
typedef struct FOOD{
ONEFOOD onefood[FOODNUM];
int foodNum;
}FOOD;
const DELTA_MOVE delta_move[4] = {
{0,-1},
{0,1},
{-1,0},
{1,0}
};
const char *printDiretion = "^V<>";
short pointStata[2000];
void no_text_cursor();
void init();
int enSureIndex(int key);
void changePoint(SNAKE_BODYS *snakePoint, SNAKE_BODYS snakeNickPoint, int direction);
void gotoRientation(int *finished, SNAKE *snakePoint, int *foodNum);
int checkWrong(SNAKE_BODYS snackhead);
void prodectFood(FOOD *food);
void showfoods(FOOD *food);
int checkfood(SNAKE_BODYS snakeHead, int *foodNum);
void showfoods(FOOD *food){
int i;
int row;
int col;
for(i = 0;i<FOODNUM;i++) {
row = (*food).onefood[i].index%80+1;
col = (int)((*food).onefood[i].index/80)+1;
gotoxy(row,col);
printf("#");
}
}
void prodectFood(FOOD *food) {
int validspace = 0;
short space[2000];
int i;
int random;
int foodindex;
int tmp;
for(i = 0;i<2000;i++) {
if(pointStata[i] == 0) {
space[validspace] = i;
validspace++;
}
}
for(i = 0;i<FOODNUM;i++) {
random = rand()%validspace;
foodindex = space[random];
(*food).onefood[i].index = foodindex;
pointStata[foodindex] = HAVA_ONEFOOD;
tmp = space[foodindex];
space[foodindex] = space[validspace-1];
space[validspace-1] = tmp;
(*food).foodNum++;
validspace--;
}
showfoods(food);
}
int checkWrong(SNAKE_BODYS snackhead) {
int tmpindex = (snackhead.bodycol-1)*80+snackhead.bodyrow-1;
if(pointStata[tmpindex] == HAVA_OBSTACLE) {
return OVERINDEX;
}
return NO_OVERINDEX;
}
int checkfood(SNAKE_BODYS snakeHead, int *foodNum) {
int tmpindex = (snakeHead.bodycol-1)*80+snakeHead.bodyrow-1;
if(pointStata[tmpindex] == HAVA_ONEFOOD) {
(*foodNum)--;
return 1;
}
return 0;
}
void changePoint(SNAKE_BODYS *snakeheadPoint, SNAKE_BODYS snakeNickPoint,int direction) {
(*snakeheadPoint).bodyrow = snakeNickPoint.bodyrow+delta_move[direction].deltarow;
(*snakeheadPoint).bodycol = snakeNickPoint.bodycol+delta_move[direction].deltacol;
}
void gotoRientation(int *finished, SNAKE *snakePoint, int *foodNum) {
SNAKE snake = *snakePoint;
int snakeNick = snake.headIndex;
int brittleStar = ((snake.headIndex)-(snake.length)+snake.maxlength+1)%(snake.maxlength);
int tmpindex;
if(snake.curlength < snake.length){
(snake.curlength)++;
}
if(snake.curlength >1) {
gotoxy(snake.snake_bodys[snakeNick].bodyrow,snake.snake_bodys[snakeNick].bodycol);
printf("*");
}
if(snake.curlength>=snake.length) {
gotoxy(snake.snake_bodys[brittleStar].bodyrow,snake.snake_bodys[brittleStar].bodycol);
tmpindex = (snake.snake_bodys[brittleStar].bodycol-1)*80+snake.snake_bodys[brittleStar].bodyrow-1;
pointStata[tmpindex] = 0;
printf(" ");
}
snake.headIndex = (((snake.headIndex)+1)%(snake.maxlength));
changePoint(&(snake.snake_bodys[snake.headIndex]),snake.snake_bodys[snakeNick],snake.direction);
snake.length = snake.length+checkfood(snake.snake_bodys[snake.headIndex],foodNum)*3;
*finished = !checkWrong(snake.snake_bodys[snake.headIndex]);
gotoxy(snake.snake_bodys[snake.headIndex].bodyrow,snake.snake_bodys[snake.headIndex].bodycol);
tmpindex = (snake.snake_bodys[snake.headIndex].bodycol-1)*80+snake.snake_bodys[snake.headIndex].bodyrow-1;
pointStata[tmpindex] = 11;
printf("%c", printDiretion[snake.direction]);
*snakePoint = snake;
}
int enSureIndex(int key) {
if(key == KEY_UP) {
return 0;
}else if(key == KEY_DOWN) {
return 1;
}else if(key == KEY_LEFT) {
return 2;
}else if(key == KEY_RIGHT) {
return 3;
}else if(key == KEY_PGUP) {
return 4;
}else if(key == KEY_PUDN) {
return 5;
}else {
return 10;
}
}
void init() {
int i;
int j;
int tmpindex;
for(i = 1;i<=80;i++) {
for(j = 1;j<=25;j++) {
if(i == 1 && j == 1) {
gotoxy(i,j);
printf("%c",201);
tmpindex = (j-1)*80+i-1;
pointStata[tmpindex] = HAVA_OBSTACLE;
}else if(i>1 && i<80 && (j ==1 || j == 25) ) {
gotoxy(i,j);
printf("%c",205);
tmpindex = (j-1)*80+i-1;
pointStata[tmpindex] = HAVA_OBSTACLE;
}else if(i == 80 && j == 1 ) {
gotoxy(i,j);
printf("%c",187);
tmpindex = (j-1)*80+i-1;
pointStata[tmpindex] = HAVA_OBSTACLE;
}else if((i == 1 || i == 80) && j >1 && j<25 ) {
gotoxy(i,j);
printf("%c",186);
tmpindex = (j-1)*80+i-1;
pointStata[tmpindex] = HAVA_OBSTACLE;
}else if(i == 1 && j == 25 ) {
gotoxy(i,j);
printf("%c",200);
tmpindex = (j-1)*80+i-1;
pointStata[tmpindex] = HAVA_OBSTACLE;
}else {
tmpindex = (j-1)*80+i-1;
pointStata[tmpindex] = 0;
}
}
}
tmpindex = 1999;
pointStata[tmpindex] = HAVA_OBSTACLE;
}
void no_text_cursor() {
union REGS r;
r.h.ah=1;
r.h.ch=32;
int86(0x10,&r,&r);
}
int main() {
int newkey = 0;
int lastkey = 0;
long int j = 0;
int direction;
int validdirection;
int finished = 0;
int speed = 800;
SNAKE snake ={
0,
5,
0,
MAX_LENGTH,
3,
{{40,12}}
};
FOOD foods;
clrscr();
init();
no_text_cursor();
srand(time(NULL));
foods.foodNum = 0;
while(!finished) {
if(foods.foodNum == 0) {
prodectFood(&foods);
}
newkey = bioskey(1);
if(newkey != 0) {
if(newkey == KEY_ESC) {
finished = 1;
bioskey(0);
}else{
lastkey = bioskey(0);
direction = enSureIndex(lastkey);
if(direction>=0 && direction<6) {
validdirection = direction;
if(direction < 4) {
snake.direction = validdirection;
}
}
}
}
if(direction == 4 ) {
speed += 50;
direction = snake.direction;
}else if(direction == 5 && speed >51){
speed -= 50;
direction = snake.direction;
}
if(j>(TIME/speed)) {
gotoRientation(&finished,&snake,&(foods.foodNum));
j = 0;
if(snake.length>=MAX_LENGTH){
gotoxy(40,12);
printf("you Won!!!\n");
getch();
finished = 1;
}
}
j++;
}
if(finished) {
clrscr();
gotoxy(20,12);
printf("GameOver!!!Press ENTER key to end!!!\n");
newkey = bioskey(1);
while(newkey != KEY_ENTER){
newkey = bioskey(0);
}
}
return 0;
}