一时兴起,想做点好玩的,又能复习点知识,所以决定做一个用c语言实现小游戏的主题文章,所有游戏的实现均为自己对游戏的理解而制作,没有参考别人的逻辑,所有可能不太完善,如有想添加的功能,可以与我探讨,或者自己进一步开发。
由于游戏采用的是针对首和尾部的操作,所以不会出现频繁刷图带来的频闪,也不会随着链表的长度带来时间上升而产生的时间不一。
编译环境为: Dev C++ windows10
以下为游戏实录:
童年回忆 贪吃蛇
**注意事项:**运动间隔时间在"snack.h"头文件的 #define SNACK_TIME 0.3 中设置,如果自己想要实现随着蛇身长度的变化线性改变运动速度亦可以通过此端口进行设置,蛇身采用双向循环链表,表头head->id 存储的是蛇的总长度。
想实现每次节点都不一样,可以自己更改种子
- 绘图代码部分:print.h文件
#ifndef _PRINT_H_
#define _PRINT_H_
#include<time.h>
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <windows.h>
#define time_span 0.1 //时间刷新间隔
void HideCursor();
void modeset(int h,int w);
void color(short x);
void gotoxy(int y,int x);
void print_time(clock_t start,float *p_stop_temp,int n, int sign_time);
void print_char(int x,int y,char *c);
#endif
- 1 绘图部分
void HideCursor() //隐藏光标
{
CONSOLE_CURSOR_INFO curInfo; //定义光标信息的结构体变量
curInfo.dwSize = 1; //如果没赋值的话,隐藏光标无效
curInfo.bVisible = FALSE; //将光标设置为不可见
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄
SetConsoleCursorInfo(handle, &curInfo); //设置光标信息
}
void color(short x) //自定义函根据参数改变颜色
{
if(x>=0 && x<=15)//参数在0-15的范围颜色
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), x); //只有一个参数,改变字体颜色
else//默认的颜色白色
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7);
}
void gotoxy(int y,int x) //移动光标到相应的位置
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos1;
COORD pos2;
pos1.X = x;
pos1.Y = y;
pos2.X = x+1;
pos2.Y = y;
SetConsoleCursorPosition(handle,pos1);
SetConsoleCursorPosition(handle,pos2);
}
void modeset(int h,int w) //界面大小 ,给的数不用太大
{
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出的句柄,命令行的程序会把字符输出到屏幕上。
COORD size = {w, h};//设置窗口的大小。
SetConsoleScreenBufferSize(hOut,size);//重新设置缓冲区大小。
SMALL_RECT rc = {0,0, w, h};//重置窗口位置和大小。
SetConsoleWindowInfo(hOut ,1 ,&rc);//重置窗口大小
return;
}
void print_char(int x,int y,char *c) //变为路线
{
x=x+1;
y=y+1;
color(16);
y=y-1;
gotoxy(x,2*y-1); //光标数组y坐标的移动是按照char 1字节 移动 所以覆盖两个坐标才能覆盖相应的文字
printf("");
gotoxy(x,2*y+1); //光标数组y坐标的移动是按照char 1字节 移动 所以覆盖两个坐标才能覆盖相应的文字
HideCursor();
if(strcmp(c,"身" )==0)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 170);
printf("%s",c);
}
else if(strcmp(c,"墙" )==0)
{ //没有很好的适配,为了方便操作,对道路用全黑汉字,
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 119);
printf("%s",c);
}
else if(strcmp(c,"米" )==0)
{ //没有很好的适配,为了方便操作,对道路用全黑汉字,
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 172);
printf("%s",c);
}
else if(strcmp(c," " )==0)
{
color(16);
printf("%s",c);
}
else if(strcmp(c,"头" )==0)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 327);
printf("%s",c);
}
color(16);
gotoxy(-1,-1);
}
void print_time(clock_t start,float *p_stop_temp, int len, int sign_time) //显示时间函数,间隔超过0.1才会刷新,避免闪烁
{
float duration;
float t;
clock_t stop = clock(); //目前时间
t = ((float)stop)/CLK_TCK;
if( t - *p_stop_temp > time_span && sign_time != 0 )
{
gotoxy(-1,-1);
duration = ((float)(stop-start))/CLK_TCK; //当前时间
color(16);
gotoxy(8,2*N+14);
printf(" ");//覆盖原来的计数
HideCursor();
gotoxy(8,2*N+14); //光标数组y坐标的移动是按照char 1字节 移动 所以覆盖两个坐标才能覆盖相应的文字
printf("%.1lf",duration);
HideCursor();
gotoxy(10,2*N+14);
printf(" ");//覆盖原来的计数
gotoxy(10,2*N+14); //光标数组y坐标的移动是按照char 1字节 移动 所以覆盖两个坐标才能覆盖相应的文字
printf("%d",len);
HideCursor();
*p_stop_temp=t;
}
}
- snack.h文件
#ifndef _SNACK_H_
#define _SNACK_H_
#include<stdio.h>
#include<windows.h>
#include<time.h>
#include<stdlib.h>
#define N 30 //游戏区域
#define M 30
#define snack_time 0.3
typedef struct snake{
int id;
int size_x;
int size_y;
struct snake* prv;
struct snake* next;
}Snack;
Snack* randsnack(int (*maze_sign)[N] ,Snack* head);
Snack* creathead( int (*maze_sign)[N] ,Snack* head);
Snack* snack_sport(clock_t start ,int (*maze_sign)[N], Snack* head, int direction,int n,int *prv_dir);
#endif
Snack* creathead( int (*maze_sign)[N] ,Snack* head)
{
Snack* head1=(Snack*)malloc(sizeof(Snack));
Snack* head2=(Snack*)malloc(sizeof(Snack));
if(head==NULL||head1==NULL)
{
printf("头结点创建失败!\n");
return NULL;
}
head1->id = 1; //头节点保存蛇身长度
head1->size_x = N/2 ; //初始位置在中间 ,移动方向为向右
head1->size_y = M/2 ;
maze_sign[head1->size_x][head1->size_y] = 1; // 头
print_char(head1->size_x,head1->size_y,"头");
head2->id = 1;
head2->size_x = N/2 ; //身体1
head2->size_y = M/2-1 ;
maze_sign[head2->size_x][head2->size_y] = 1; //食物为2 身为1
print_char(head2->size_x,head2->size_y,"身");
head1->next=head2;
head1->prv= head2;
head2->next = head1;
head2->prv = head1;
head=randsnack(maze_sign , head1); //生成食物 2 ,并链接带蛇尾部
return head;
}
void init_menu( int (*maze)[N] ) //显示区域
{
int i,j;
for(i=0;i<M;i++)
{
for(j=0;j<N;j++)
{
if(i==0||j==0||i==M-1||j==N-1)
{
maze[i][j]=1;
print_char( i , j, "墙" );
}
}
}
gotoxy(8,2*N+4);
color(16);
printf("游戏计时:");//覆盖原来的计数
HideCursor();
gotoxy(10,2*N+4);
color(16);
printf("蛇身长度:");//覆盖原来的计数
HideCursor();
}
Snack* randsnack(int (*maze_sign)[N], Snack* head ) //,head 链接尾结点 ***************************************************************************************
{
int x,y;
static int t= (M-2)*(N-2); //剩余位置
P: x = 1 + rand() % (M - 2);
y = 1 + rand() % (N - 2);
//防止在蛇身上生成食物
if( maze_sign[x][y] == 1 && t>1)
{
goto P;
}
if( maze_sign[x][y] != 1 && t>1)
{
t--;
goto Q;
}
if( t<=1 )
{
gotoxy(18,2*N+4); //光标数组y坐标的移动是按照char 1字节 移动 所以覆盖两个坐标才能覆盖相应的文字
printf("恭喜完成游戏!!!\n");
while(1);//没有位置可以继续生长了
}
Q: print_char(x,y,"米");
Snack* new_node=(Snack*)malloc(sizeof(Snack));
if(new_node==NULL)
{
printf("身结点创建失败!\n");
return NULL;
}
maze_sign[x][y] = 2; //2 为食物
head->id=head->id+1;
new_node->id = head->id ; //第几个食物
new_node->size_x = x;
new_node->size_y = y;
head->prv->next=new_node;
new_node->prv = head->prv;
new_node->next =head; //提前进行链接
head->prv=new_node;
return head;
}
Snack* snack_sport(clock_t start ,int (*maze_sign)[N], Snack* head, int direction,int n,int *prv_dir)
{
clock_t stop = clock(); //目前时间
static float duration=0;
Snack* pc=head; //
float time = ((float)(stop-start))/CLK_TCK;
if(time - duration > snack_time ) //当时间间隔为 snack_time 开始执行行走,
{
Snack* temp=(Snack *)malloc(sizeof(Snack));
temp->size_x=head->prv->prv->size_x; //临时存储蛇尾结点信息,
temp->size_y=head->prv->prv->size_y;
duration = time;
/********************************************************************************************/ //向右运动
if(direction==1 && *prv_dir!=2)
{
*prv_dir=1;
//采用循环链表
print_char(pc->size_x,pc->size_y,"身" ); //将头转化为身体
pc=pc->prv->prv; //先指向尾结点
maze_sign[pc->size_x][pc->size_y] = 0;
print_char(pc->size_x,pc->size_y," " ); //尾结点位置清空
while(pc!=head)
{
pc->size_x = pc->prv->size_x; //所有坐标位置后移
pc->size_y = pc->prv->size_y;
pc=pc->prv;
}
head->size_x = head->size_x; //生成新的头位置
head->size_y = head->size_y + 1;
}
/********************************************************************************************/ //向左运动
if(direction==2 &&*prv_dir!=1)
{
*prv_dir=2;
print_char(pc->size_x,pc->size_y,"身" ); //将头转化为身体
pc=pc->prv->prv; //先指向尾结点
maze_sign[pc->size_x][pc->size_y] = 0;
print_char(pc->size_x,pc->size_y," " ); //尾结点位置清空
while(pc!=head)
{
pc->size_x = pc->prv->size_x;
pc->size_y = pc->prv->size_y;
pc=pc->prv;
}
head->size_x = head->size_x;
head->size_y = head->size_y - 1;
}
/********************************************************************************************/ //向下运动
if(direction==3&& *prv_dir!=4)
{
*prv_dir=3;
print_char(pc->size_x,pc->size_y,"身" ); //将头转化为身体
pc=pc->prv->prv; //先指向尾结点
maze_sign[pc->size_x][pc->size_y] = 0;
print_char(pc->size_x,pc->size_y," " ); //尾结点位置清空
while(pc!=head)
{
pc->size_x = pc->prv->size_x;
pc->size_y = pc->prv->size_y;
pc=pc->prv;
}
head->size_x = head->size_x+1;
head->size_y = head->size_y ;
}
/********************************************************************************************/ //向上运动
if(direction==4 && *prv_dir!=3)
{
*prv_dir=4;
print_char(pc->size_x,pc->size_y,"身" ); //将头转化为身体
pc=pc->prv->prv; //先指向尾结点
maze_sign[pc->size_x][pc->size_y] = 0;
print_char(pc->size_x,pc->size_y," " ); //尾结点位置清空
while(pc!=head)
{
pc->size_x = pc->prv->size_x;
pc->size_y = pc->prv->size_y;
pc=pc->prv;
}
head->size_x = head->size_x-1;
head->size_y = head->size_y ;
}
/************************************************************************/ //碰撞检测
if( maze_sign[head->size_x][head->size_y] == 1 )
{
gotoxy(15,2*N+4);
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 327);
printf("发生碰撞,游戏结束!\a");
print_char(head->size_x,head->size_y,"头" ); //绘制蛇头
while(1);
}
/**************************************************************************/ //碰撞检测完成后更新蛇头信息
maze_sign[head->size_x][head->size_y]=1; //位置信息更新
print_char(head->size_x,head->size_y,"头" ); //绘制蛇头
/***************************************************************************/ //食物检测
if( head->size_x == head->prv->size_x && head->size_y==head->prv->size_y )
{
maze_sign[head->prv->size_x][head->prv->size_y]=1;
head->prv->size_x = temp->size_x;
head->prv->size_y = temp->size_y;
print_char(head->prv->size_x,head->prv->size_y,"身" );
head=randsnack(maze_sign, head ) ;
}
}
return head;
}
- main.c
#include"main.h"
#include"snack.h"
clock_t start; //时间开始参数
int main()
{
Snack *new_node;
float stop_temp = 0; //避免过快的刷新造成闪烁,限定时间刷新间隔 。
float *p_stop_temp = &stop_temp; //指针操作
int sign_time = 1; //时间标志位,游戏结束计时停止
int Pre_num = 0;
int *prv_dir = &Pre_num;
int maze[M][N] = {0}; //初始化游戏界面
int maze_sign[M][N] = {0};
int direction = 3; //初始向右移动 //右 1 左2 下3 上4
modeset(M+1, 2*N+30) ; //显示区域限制
init_menu(maze_sign); //初始化显示游戏界面
Snack *head = creathead(maze_sign, head); //创建头结点
while (1)
{
/***********游戏计时显示*********/
print_time(start, p_stop_temp, head->id, sign_time); //显示游戏时间
/**避免输入堵塞**/
if( (GetKeyState(38 )<0) ||(GetKeyState('W')<0) ||(GetKeyState('w')<0)) //上 4
if(*prv_dir != 3)
direction = 4;
if( (GetKeyState(40 )<0) ||(GetKeyState('S')<0) ||(GetKeyState('s')<0)) //下 3
if(*prv_dir != 4)
direction = 3;
if( (GetKeyState(37 )<0) ||(GetKeyState('A')<0) ||(GetKeyState('a')<0)) //左 2
if(*prv_dir !=1 )
direction = 2;
if( (GetKeyState(39 )<0) ||(GetKeyState('D')<0) ||(GetKeyState('d')<0)) //右 1
if(*prv_dir != 2)
direction = 1;
print_time(start , p_stop_temp, head->id,sign_time); //显示游戏时间
head = snack_sport(start ,maze_sign, head, direction,head->id , prv_dir); //运动
}
return 0;
}