C语言编程实战——编写简单贪吃蛇程序
2017-07-24 15:05
651 查看
心之何如,有似万丈迷津,遥亘千里,其中并无舟子可渡人,除了自渡,他人爱莫能助。
—-三毛
编程环境:VC++
实例:该程序实现将“Helloword”打印到固定坐标。
GetStdHandle()函数用于从一个特定的标准设备(标准输入、标准输出或标准错误)中取得一个句柄(用来标识不同设备的数值)。可以嵌套使用。每次设置光标之前必须先调用获得一个值hOut,所以可以把hOut定义成全局变量。
实例:程序一直打印HelloWorld,直到按“ESC”结束。
我们通常通过伪随机数生成器提供一粒新的随机种子。函数 srand()(来自stdlib.h)可以为随机数生成器播散种子
%i和%d都是表示有符号十进制整数,但%i可以自动将输入的八进制(或者十六进制)转换为十进制,而%d则不会进行转换。
比如我调用
蛇头坐标为(5,3)
移动的主要思想就是通过单位时间上的位移来实现的,也相当于设定时间的画线,不过每次移动都需要把之前的蛇清除,然后再打印一次,让我们看起来就像有一条蛇在动。
吃到自己挂掉:蛇头坐标和身体任何部位坐标相等
撞到障碍物挂掉:蛇头坐标和障碍物坐标相等
运行效果:
比如最开始虽然实现了贪吃蛇的移动问题,通过网上搜索也学会了使用清屏函数system(“cls”)去清除移动过程中的的轨迹,但是到最后发现了个问题,如果使用这个函数的话,是清除整个屏幕,那我画好的边界不就没了么?通过询问老师,后来得到了解决方法,就是通过打印空格来实现清除,当时就恍然大悟。然后又把代码推翻重新去写清除这部分代码了。
第二个问题就是当时不知道用kbhit()这个函数来捕捉按键,自己用直接通过switch来判断输入按键的值,来用for循环来控制移动,然后发现发现这样很不科学,要等for循环结束以后它才会再一次捕捉按键。后来发现竟然有kbhit()这么好使,所以问题就变得简单了。
问题是当时想着控制它转弯不那么别扭,最开始的想法是通过坐标的形式,因为当时是想着蛇头的坐标始终比后一个坐标大一个单位,所以想让蛇身依次每个向前多移动一步,但是没有考虑到时间问题,只考虑空间问题,后来发现是并排往下走的。后来经过老师指点,原来思想是蛇头动,带动后面的,没移动一步,把前面一点的位置信息赋值给下一个点,这样就是后面的点在重复前面相邻点的运动。。
—-三毛
编程环境:VC++
一、相关结构体以及函数:
1、Windows下坐标结构体COORD:
COORD是Windows API中定义的一种结构,表示一个字符在控制台屏幕上的坐标。其定义为:typedef struct _COORD { SHORT X; // horizontal coordinate SHORT Y; // vertical coordinate } COORD;
2、SetConsoleCursorPosition()函数:
Windows API中设置光标坐标的函数。头文件为#include < windows.h>实例:该程序实现将“Helloword”打印到固定坐标。
#include<stdio.h> #include<windows.h> int main() { HANDLE hOut; COORD pos={15,5}; hOut=GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleCursorPosition(hOut,pos); //将光标位置设置为(15,5) printf("HelloWorld!\n"); return 0; }
GetStdHandle()函数用于从一个特定的标准设备(标准输入、标准输出或标准错误)中取得一个句柄(用来标识不同设备的数值)。可以嵌套使用。每次设置光标之前必须先调用获得一个值hOut,所以可以把hOut定义成全局变量。
3、kbhit()按键捕捉函数:
检查当前是否有键盘输入,若有则返回一个非0值,否则返回0 。实例:程序一直打印HelloWorld,直到按“ESC”结束。
#include<stdio.h> #include<conio.h> #include<stdlib.h> int main(void) { char ch; while(ch!=27) //27为按键ESC的ASCII码 { printf("HelloWorld\n"); if(kbhit()) //捕捉按键 ch=getch(); //将捕捉到的按键赋值给ch } printf("End!\n"); system("pause"); return 0; }
4、rand()函数:
rand()函数是产生随机数的一个随机函数。包含于头文件#include< stdlib.h >中。我们通常通过伪随机数生成器提供一粒新的随机种子。函数 srand()(来自stdlib.h)可以为随机数生成器播散种子
#include <stdio.h> #include <stdlib.h> int main() { unsigned int seed; /*申明初始化器的种子,注意是unsigned int 型的*/ int k; printf("Enter a positive integer seed value: \n"); scanf("%u",&seed); srand(seed); //srand函数是随机数发生器的初始化函数 printf("Random Numbers are:\n"); for(k = 1; k <= 10; k++) { printf("%i",rand()); //若是rand()%n表示生成n以内的所有整数 printf("\n"); } return 0; }
%i和%d都是表示有符号十进制整数,但%i可以自动将输入的八进制(或者十六进制)转换为十进制,而%d则不会进行转换。
二、分模块写程序:
1、绘制边框draw_frame():
因为我这里边框有游戏边框,和计分的边框两个,所以我封装了一个函数,方便直接调用。注意,这一Y轴正半轴是往下的,区别于我们数学中的坐标。void draw_frame(int W,int H,int initX,int initY) //顺时针画框函数W、H为长度高度;initX ,initY为起点坐标 { COORD pos1={initX,initY}; int i; for(i= 0; i< W;i++) { pos1.X++; //先不断往右移动 SetConsoleCursorPosition(hOut,pos1); printf("."); } for(i= 0; i< H;i++) { pos1.Y++; //再向下 SetConsoleCursorPosition(hOut,pos1); printf("."); } for(i= 0; i< W;i++) //再向左 { pos1.X--; SetConsoleCursorPosition(hOut,pos1); printf("."); } for(i= 0; i< H;i++) { pos1.Y--; //再向上 SetConsoleCursorPosition(hOut,pos1); printf("."); } }
比如我调用
draw_frame(30,20,0,0);得到如下结果:
2、计分框:score_frame()
主要是调用上面画框函数,但是还需要作相关处理,因为要打印“得分”,以及还要能够更新数据。void score_frame() { //计分方框 draw_frame(10,5,WIDTH+10,0); //调用上面画框函数画一个宽度为5,高度为10,起始坐标为(WIDTH+10,0)的框。这里我们把起始坐标放在了原来边框右边10单位初,因为是顺时针画,最终回到(WIDTH+10,0)这个点,所以只要起始点X轴大于WIDTH就可以了。 //将光标挪到方框内,打印“得分:”提示字符 COORD pos1={WIDTH+11,1}; SetConsoleCursorPosition(hOut,pos1); printf("得分:"); //将光标继续挪动,用于计分: COORD pos2={WIDTH+12,2}; //写计分位置的初始坐标,因为之前方框的起始坐标为(10,1),所以这里都加1。 SetConsoleCursorPosition(hOut,pos2); printf("%d",score); }
3、初始化贪吃蛇身:
想要画出一条蛇,一个点一个点画,将每个点的坐标都保存在一个数组里面。void InitSnake(COORD SnakeHead) { int i; COORD temp = SnakeHead; //蛇头的起始坐标 for( i=0 ; i< InitLEN ; i++ ) { arr[i]=temp; //通过数组来存储蛇,方便后面显示 SetConsoleCursorPosition(hOut,temp); temp.X--; //打印一截,光标移动,避免覆盖打印在同一个点上。 } SnakeLEN = InitLEN; //InitLEN是一个宏定义,蛇的初始长度 }
4、显示蛇身:
void showSnake() { int i; for( i=0; i < SnakeLEN;i++) { SetConsoleCursorPosition(hOut,arr[i]); //设置光标位置 if(i == 0) { printf("@"); //最先打印的是蛇头 } else { printf("*"); 蛇身 } } }
蛇头坐标为(5,3)
4、用随机数产生随机食物:
void creatFood() { srand((unsigned)time(NULL)); int x = (rand()%(WIDTH-1)+1); //-1为了产生随机数小于WIDTH,+1为了使产生随机数大于0 int y = (rand()%(HIGH-1)+1); food_pos.X =x; //food_pos是一个COORD变量 food_pos.Y =y; SetConsoleCursorPosition(hOut,food_pos); printf("o"); //打印食物图样 }
5.1、清除蛇身函数clearSnake()
void clearSnake() { int i; for( i=0; i < SnakeLEN;i++) { SetConsoleCursorPosition(hOut,arr[i]); printf(" "); //打印空格来覆盖之前的轨迹。 } }
5.2、移动move():
上面画出了一条蛇,但是是静态的,我们要想办法让它动起来,动次动次,小火车要跑起来了。移动的主要思想就是通过单位时间上的位移来实现的,也相当于设定时间的画线,不过每次移动都需要把之前的蛇清除,然后再打印一次,让我们看起来就像有一条蛇在动。
void move(int dir) { int i; Sleep(100); //通过单位时间上位移来控制移动 clearSnake(); //清除走过的轨迹 //将前一个点的状态给后一个点,当i=SnakeLEN-2,将蛇头状态赋给第二个值。所以每隔100ms,后一个点的坐标就等于前一个点的坐标了。 for( i=0;i<SnakeLEN-1;i++) { arr[SnakeLEN-1-i] = arr[SnakeLEN-2-i]; } //这里RIGHT 、LEFT、UP、DOWN是宏定义,分别存放d a s w的ascii码 switch(dir){ case RIGHT: { arr[0].X++; //蛇头运动 break; } case LEFT: { arr[0].X--; break; } case UP: { arr[0].Y--; break; } case DOWN: { arr[0].Y++; break; } default: break; } die(); //死亡的几种方式,下文作做介绍 //吃食物: if(food_pos.X == arr[0].X && food_pos.Y == arr[0].Y ) //头的坐标和食物坐标相同(吃食物变长) { creatFood(); //产生随机食物 SnakeLEN++; //蛇身长度增加 COORD pos2={WIDTH+12,2}; //写计分位置的初始坐标,因为之前方框的起始坐标为(10,1),所以这里都加1。 SetConsoleCursorPosition(hOut,pos2); score++; printf("%d",score); printf(" "); //打印空白用于覆盖上一次计分 } showSnake(); //因为每次移动都会清除之前的蛇,所以每次都需要再打印一次。 }
6、死亡的几种方式:
撞到墙挂掉:蛇头坐标超出方框吃到自己挂掉:蛇头坐标和身体任何部位坐标相等
撞到障碍物挂掉:蛇头坐标和障碍物坐标相等
void die() { int i; for(i = 1;i < SnakeLEN-1;i++ ) //吃到自己,就会死掉。 { if(arr[0].X == arr[i].X && arr[0].Y == arr[i].Y) { myexit(); } } if(arr[0].X >= WIDTH || arr[0].X <= 0 || arr[0].Y >= HIGH || arr[0].Y <= 0 ) //撞墙就死掉 { myexit(); } for(i = 0;i < 50;i++ ) //撞到障碍物,死掉 { if(arr[0].X == tmp_arr[i].X && arr[0].Y == tmp_arr[i].Y) { myexit(); //GAME OVER } } }
7、产生障碍物:
void barrier_save() //保存所有生成的障碍物的点(所有障碍物由点组成) { tmp_arr[barrier_num]=tmp_barrier; barrier_num++; //全局变量 } void creatBarrier(COORD pos,int dir,int len) { int i =0; tmp_barrier=pos; for(;i < len; i++) { static int j=0; SetConsoleCursorPosition(hOut,tmp_barrier); printf("+"); if(dir == 1 ) //dir的为0为1的值为二分之一,为了让它产生横向和纵向的障碍物概率相同。 { tmp_barrier.X++; barrier_save(); //每次改变都会获得一个新的点,需要把这个点记录下来。 } else { tmp_barrier.Y++; barrier_save(); } barrier_save(); } }
void crossWall() { srand((unsigned)time(NULL)); int i =0; for ( ; i < 5 ; i++) { //随机数产生障碍物 tmp_wall.X=(rand()%(WIDTH-3)); tmp_wall.Y=(rand()%(HIGH-3)); half = (rand()%2+1); //随机获得 1 或者 2 ,两者概率都为二分之一 creatBarrier(tmp_wall,half,3); //产生一个长度为3的障碍物 } }
8、键盘控制:
这里主要是通过kbhit()函数来捕捉按键,这很关键,让我少走了很多弯路。void kbctrl() { char ch = 'd'; //设定起始按键为‘d’,为了让它开始就向右走 int dir=RIGHT; //设定起始移动方向 while(1) { if(kbhit()) //加入键盘控制,判断是否有按键 { ch=getch(); } switch(ch) //判断是否是按键 { case 'd': { dir = RIGHT; move(dir); break; //跳出以后依旧可以保持原来状态 } case 's': { dir = DOWN; move(dir); break; } case 'a': { dir = LEFT; move(dir); break; } case 'w': { dir = UP; move(dir); break; } default: //按其他键,保持原来状态 { move(dir); break; } } } }
完整代码:
// myproject.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <stdio.h> #include <stdlib.h> #include <windows.h> #include <conio.h > #include <time.h> #define InitLEN 10 #define MAXLEN 100 #define HIGH 20 #define WIDTH 40 int SnakeLEN=0; int score=0; //得分 int barrier_num =0; //所有障碍物组成点的个数 int half; //#define HPRIZON 1 //#define VERTICAL 2 COORD arr[MAXLEN]; COORD tmp_arr[50]; COORD food_pos; COORD map_pos; COORD tmp_wall; COORD tmp_barrier; #define RIGHT 39 #define LEFT 37 #define UP 38 #define DOWN 40 HANDLE hOut=GetStdHandle(STD_OUTPUT_HANDLE); void clearSnake(); void showSnake(); void creatFood(); void creatMap(); void die(); void barrier_save(); void creatBarrier(COORD pos,int dir,int len); void crossWall(); struct wall{ int dir; int len; COORD bPos; }; struct wall wall_arr[50]; void draw_frame(int W,int H,int initX,int initY) //顺时针画框函数W、H为长度高度;initX ,initY为其实坐标 { COORD pos1={initX,initY}; int i; for(i= 0; i< W;i++) { pos1.X++; SetConsoleCursorPosition(hOut,pos1); printf("."); } for(i= 0; i< H;i++) { pos1.Y++; SetConsoleCursorPosition(hOut,pos1); printf("."); } for(i= 0; i< W;i++) { pos1.X--; SetConsoleCursorPosition(hOut,pos1); printf("."); } for(i= 0; i< H;i++) { pos1.Y--; SetConsoleCursorPosition(hOut,pos1); printf("."); } } void InitSnake(COORD SnakeHead) { int i; COORD temp = SnakeHead; for( i=0 ; i< InitLEN ; i++ ) { arr[i]=temp; SetConsoleCursorPosition(hOut,temp); printf("@"); temp.X--; } SnakeLEN = InitLEN; } void move(int dir) { int i; Sleep(100); clearSnake(); //将蛇头状态给蛇身 for( i=0;i<SnakeLEN-1;i++) { arr[SnakeLEN-1-i] = arr[SnakeLEN-2-i]; } switch(dir){ case RIGHT: { arr[0].X++; //蛇头运动 break; } case LEFT: { arr[0].X--; break; } case UP: { arr[0].Y--; break; } case DOWN: { arr[0].Y++; break; } default: break; } die(); if(food_pos.X == arr[0].X && food_pos.Y == arr[0].Y ) //头的坐标和食物坐标相同(吃食物变长) { creatFood(); SnakeLEN++; COORD pos2={WIDTH+12,2}; //写计分位置的初始坐标,因为之前方框的起始坐标为(10,1),所以这里都加1。 SetConsoleCursorPosition(hOut,pos2); score++; printf("%d",score); printf(" "); //打印空白用于覆盖上一次计分 } //show snake showSnake(); } void showSnake() { int i; for( i=0; i < SnakeLEN;i++) { SetConsoleCursorPosition(hOut,arr[i]); if(i == 0) { printf("@"); } else { printf("*"); } } } void clearSnake() { int i; for( i=0; i < SnakeLEN;i++) { SetConsoleCursorPosition(hOut,arr[i]); printf(" "); } } void barrier_save() //保存所有生成的障碍物的点(所有障碍物由点组成) { tmp_arr[barrier_num]=tmp_barrier; barrier_num++; //全局变量 } void creatBarrier(COORD pos,int dir,int len) { int i =0; tmp_barrier=pos; for(;i < len; i++) { static int j=0; SetConsoleCursorPosition(hOut,tmp_barrier); printf("+"); if(dir == 1 ) { tmp_barrier.X++; barrier_save(); //每次改变都会获得一个新的点,需要把这个点记录下来。 } else { tmp_barrier.Y++; barrier_save(); } barrier_save(); } } void kbctrl() { char ch = 'd'; int dir=RIGHT; while(1) { if(kbhit()) //加入键盘控制,判断是否有按键 { ch=getch(); } switch(ch) //判断是否是 'a' 's' { case 'd': { dir = RIGHT; move(dir); break; } case 's': { dir = DOWN; move(dir); break; } case 'a': { dir = LEFT; move(dir); break; } case 'w': { dir = UP; move(dir); break; } default: { move(dir); break; } } } } void creatFood() { srand((unsigned)time(NULL)); int x = (rand()%(WIDTH-1)+1); //-1为了产生随机数小于WIDTH,+1为了使产生随机数大于0 int y = (rand()%(HIGH-1)+1); food_pos.X =x; food_pos.Y =y; SetConsoleCursorPosition(hOut,food_pos); printf("o"); } void myexit() { COORD exit_pos={WIDTH+2,HIGH+2}; SetConsoleCursorPosition(hOut,exit_pos); printf("GAME OVER!"); exit(0); } //死亡的方式: void die() { int i; for(i = 1;i < SnakeLEN-1;i++ ) //吃到自己,就会死掉。 { if(arr[0].X == arr[i].X && arr[0].Y == arr[i].Y) { myexit(); } } if(arr[0].X >= WIDTH || arr[0].X <= 0 || arr[0].Y >= HIGH || arr[0].Y <= 0 ) //撞墙就死掉 { myexit(); } for(i = 0;i < 50;i++ ) //撞到障碍物,死掉 { if(arr[0].X == tmp_arr[i].X && arr[0].Y == tmp_arr[i].Y) { myexit(); } } } void score_frame() { //计分方框 draw_frame(10,5,WIDTH+10,0); COORD pos1={WIDTH+11,1}; SetConsoleCursorPosition(hOut,pos1); printf("得分:"); //计分 COORD pos2={WIDTH+12,2}; //写计分位置的初始坐标,因为之前方框的起始坐标为(10,1),所以这里都加1。 SetConsoleCursorPosition(hOut,pos2); printf("%d",score); } int main(int argc, char* argv[]) { system("title 贪吃蛇"); COORD pos={5,3}; crossWall(); draw_frame(WIDTH,HIGH,0,0); score_frame(); InitSnake(pos); creatFood(); /* while(1) { move(RIGHT); if(kbhit()) { ch=getch(); break; } } */ kbctrl(); printf("\n"); return 0; } void crossWall() { srand((unsigned)time(NULL)); int i =0; for ( ; i < 5 ; i++) { tmp_wall.X=(rand()%(WIDTH-3)); tmp_wall.Y=(rand()%(HIGH-3)); // tmp_arr[i] = tmp_wall; half = (rand()%2+1); //随机获得 1 或者 2 ,两者概率都为二分之一 creatBarrier(tmp_wall,half,3); } }
运行效果:
心得体会:
花了三天写了代码,由浅入深的系统的写了一个完整的小项目,还是蛮有成就感的,也进一步对C语言的知识进行巩固。在这个过程中,遇到蛮多问题的。学会了如何一步一步去解决。比如最开始虽然实现了贪吃蛇的移动问题,通过网上搜索也学会了使用清屏函数system(“cls”)去清除移动过程中的的轨迹,但是到最后发现了个问题,如果使用这个函数的话,是清除整个屏幕,那我画好的边界不就没了么?通过询问老师,后来得到了解决方法,就是通过打印空格来实现清除,当时就恍然大悟。然后又把代码推翻重新去写清除这部分代码了。
第二个问题就是当时不知道用kbhit()这个函数来捕捉按键,自己用直接通过switch来判断输入按键的值,来用for循环来控制移动,然后发现发现这样很不科学,要等for循环结束以后它才会再一次捕捉按键。后来发现竟然有kbhit()这么好使,所以问题就变得简单了。
问题是当时想着控制它转弯不那么别扭,最开始的想法是通过坐标的形式,因为当时是想着蛇头的坐标始终比后一个坐标大一个单位,所以想让蛇身依次每个向前多移动一步,但是没有考虑到时间问题,只考虑空间问题,后来发现是并排往下走的。后来经过老师指点,原来思想是蛇头动,带动后面的,没移动一步,把前面一点的位置信息赋值给下一个点,这样就是后面的点在重复前面相邻点的运动。。
相关文章推荐
- MapReduce编程实战1——WorldCout程序编写
- 编程精粹--编写高质量C语言代码(1):假想编译程序
- 实验二 用c语言编写简单程序
- python GUI编程——wxpython编写简单记事本程序
- 用C语言编写一个Linux下的简单shell程序
- 实验 2 用C语言编写简单程序
- Windows平台下GCC编程之从键盘输入10名学生的C语言成绩存入一维数组内,编写程序计算10名学生的最高分、平均分和及格人数
- socket编程之C语言一个简单监听程序
- 搞定linux上MySQL编程(六):C语言编写MySQL程序(结)
- 实验 2 用C语言编写简单程序
- 实验 2 用C语言编写简单程序
- 实验二 用c语言编写简单程序
- 编写字符小游戏——贪吃蛇程序(c语言)
- 用C语言编写 Windows 服务程序的五个步骤以及服务编程常见问题处理
- 用C语言编写简单的接口程序
- 实验2 用c语言编写简单程序
- 用C语言编写一个Linux下的简单shell程序
- 郭克华手机编程教学视频----我的练习源码(6)实战:实现一个简单的监听程序
- 采用GTK进行简单聊天程序客户端的编写,使用C语言。
- 搞定linux上MySQL编程(六):C语言编写MySQL程序(结)