制作一款贪吃蛇游戏的技术要求:
1.二维数组、简单的字符处理、
循环体、结构体、随机数
2.数据结构之队列
3.线程控制
4.输入输出流
编程思想:
1.实时显示界面可用二维数组实现,
要注意区分当前位置是蛇头、蛇身、苹果还是墙
2.贪吃蛇的蛇头和苹果位置要在程序最开始指定,
也就是蛇头和苹果的初始化工作,
蛇头可以使用随机数确定也可以写死,
苹果则必须随机指定;
3.我们输入数据必须经过缓冲区,
如果迟迟未输入必然使游戏出于阻塞状态
因此要解决两个技术问题:
(1)如何从键盘直接读取数据,
读到数据不需回车也不需显示在终端
(2)如何保证在蛇可以自动移动的同时监听键盘事件
当玩家点击方向键才会转向,否则继续前进
第一个问题可以通过临时更改输入输出流解决
第二个问题可以使用同步线程解决
主线程打印游戏数据控制主要流程
子线程控制键盘的监听
4.如何让蛇尾紧随蛇头移动,如何使蛇身可以变长
蛇身是可变长的,应当以什么方式记录蛇身数据,
个人认为选用队列非常贴切,每当蛇吃到苹果时
就会创建一个新蛇身,当蛇移动时蛇尾则要被删除
相较新生成的蛇身而言蛇尾是生成较早的蛇身
典型的先进先出的数据结构
5.要保证游戏能持续运行就必须使用循环体
6.定义坐标结构体,记录蛇头蛇身和苹果的位置
可以方便我们进行编程
首先是队列的构造:
***********************************************************
SnakeQueue.h
------------------------------
#ifndef _SNAKEQUEUE_H_
#define _SNAKEQUEUE_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <sys/types.h>
typedef struct stay_place
{
int x,y;
}Stay;
typedef struct node
{
Stay data;
struct node* next;
}Node;
typedef struct queue
{
Node* head;
Node* tail;
size_t size;
}Queue;
void init(Queue* queue);
void deinit(Queue* queue);
void enqueue(Queue* queue,Stay data);
Stay first(Queue* queue);
Stay last(Queue* queue);
Stay dequeue(Queue* queue);
bool isEmpty(Queue* queue);
int length(Queue* queue);
#endif /*_SNAKEQUEUE_H_*/
************************************************************
SnakeQueue.c
----------------------------------------
#include "SnakeQueue.h"
static Node* create(Stay data)
{
Node* node = (Node*)malloc (sizeof (Node));
node->data = data;
node->next = NULL;
return node;
}
static Node* destroy(Node* node)
{
Node* next = node->next;
free (node);
return next;
}
void init(Queue* queue)
{
queue->head=NULL;
queue->tail=NULL;
queue->size=0;
}
void deinit (Queue* queue)
{
while (queue->head)
{
queue->head=destroy(queue->head);
}
queue->tail=NULL;
queue->size=0;
}
void enqueue(Queue* queue,Stay data)
{
Node* node=create(data);
node->next=NULL;
if(!queue->head)
{
queue->head=node;
}
else
{
queue->tail->next=node;
}
queue->tail=node;
queue->size++;
}
Stay first(Queue* queue)
{
return queue->head->data;
}
Stay last(Queue* queue)
{
return queue->tail->data;
}
Stay dequeue(Queue* queue)
{
Stay data=queue->head->data;
if (!(queue->head=destroy(queue->head)))
{
queue->tail=NULL;
}
queue->size--;
return data;
}
bool isEmpty(Queue* queue)
{
return !queue->size;
}
int length(Queue* queue)
{
return queue->size;
}
***********************************************************************************
Snake.c
------------------------------------------------------------
#include <stdio.h>
#include <unistd.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/time.h>
#include "SnakeQueue.c"
#define SPACE_SIZE 10
#define WALL_SIZE 12
#define WALL -1
#define SPACE 0
#define SNAKE_HEAD 1
#define SNAKE_BODY 2
#define APPLE 3
#define SNAKE_UP 65
#define SNAKE_DOWN 66
#define SNAKE_LEFT 68
#define SNAKE_RIGHT 67
#define TURN_NEXT 500000
#define SHOW_HEAD " @"
#define SHOW_BODY " O"
#define SHOW_APPLE " $"
#define SHOW_WALL " #"
#define SHOW_SPACE " "
#define START_HEAD_X 5
#define START_HEAD_Y 5
int game[WALL_SIZE][WALL_SIZE]={SPACE};
pthread_t snake_thread;
pthread_mutex_t mut;
char ch=SNAKE_RIGHT;
void* snake_move()
{
while(1)
{
pthread_mutex_lock(&mut);
ch=getch();
if(strchr("Qq",ch))
{
break;
}
ch=getch();
ch=getch();
pthread_mutex_unlock(&mut);
}
pthread_exit(NULL);
}
void thread_start()
{
int temp;
memset(&snake_thread,0,sizeof(snake_thread));
temp = pthread_create(&snake_thread,NULL,snake_move,NULL);
}
void thread_wait()
{
if(snake_thread!=0)
{
pthread_join(snake_thread,NULL);
}
}
/*界面显示*/
void show()
{
int i,j;
for(i=0;i<12;++i)
{
for(j=0;j<12;++j)
{
if(i==0 || j==0 || i==11 || j==11)
{
game[i][j]=WALL;
printf(SHOW_WALL);
}
else if(game[i][j]==SNAKE_HEAD)printf(SHOW_HEAD);
else if(game[i][j]==SNAKE_BODY)printf(SHOW_BODY);
else if(game[i][j]==APPLE)printf(SHOW_APPLE);
else printf(SHOW_SPACE);
}
printf("\n");
}
}
/*初始化*/
Stay CreateHead()
{
Stay stay;
/*
while(1)
{
stay.x=rand()%SPACE_SIZE+1;//随机生成蛇头坐标
stay.y=rand()%SPACE_SIZE+1;//随机生成蛇头坐标
if(game[stay.x][stay.y]==SPACE)//如果当前坐标为空则生成成功,否则生成失败重新生成
{
break;
}
}
*/
stay.x=START_HEAD_X;//指定蛇头初始坐标
stay.y=START_HEAD_Y;//指定蛇头初始坐标
game[stay.x][stay.y]=SNAKE_HEAD;
return stay;
}
/*创建*/
Stay CreateApple()
{
Stay stay;
while(1)
{
stay.x=rand()%SPACE_SIZE+1;//随机生成苹果坐标
stay.y=rand()%SPACE_SIZE+1;//随机生成苹果坐标
if(game[stay.x][stay.y]==SPACE)//如果当前坐标为空则生成成功,否则生成失败重新生成
{
break;
}
}
game[stay.x][stay.y]=3;//将成功生成苹果的位置设置为苹果
return stay;
}
Stay CreateTail(Stay stay)
{
game[stay.x][stay.y]=SNAKE_BODY;//将此位置设为蛇身
return stay;//返回蛇身位置(直接将同一变量入队会导致整个队列数据都变化,因此使用函数作为中介将复制数据入队)
}
void remove_last(Stay stay)
{
game[stay.x][stay.y]=SPACE;//将此位置恢复为空
}
int MoveHead(char ch,Stay* stay)
{
game[stay->x][stay->y]=SPACE;//将旧的蛇头位置清零
switch(ch)
{
case SNAKE_UP:stay->x--;//向上移动
break;
case SNAKE_DOWN:stay->x++;//向下移动
break;
case SNAKE_LEFT:stay->y--;//向左移动
break;
case SNAKE_RIGHT:stay->y++;//向右移动
break;
default:break;
}
if(game[stay->x][stay->y]!=SPACE && game[stay->x][stay->y]!=APPLE)//如果蛇头的位置既不为空又不是苹果位置,说明蛇头撞墙或是咬到了自己
{
return -1;//返回失败状态
}
game[stay->x][stay->y]=SNAKE_HEAD;//将新位置设置为蛇头
return 0;
}
void MoveTail(Stay stay)
{
game[stay.x][stay.y]=SNAKE_BODY;//将此位置设为蛇身
}
Stay get_tail(Stay stay)
{
return stay;//返回蛇尾位置(直接将同一变量入队会导致整个队列数据都变化,因此使用函数作为中介将复制数据入队)
}
/*键盘控制*/
int getch (void)
{
struct termios old;
ioctl (STDIN_FILENO, TCGETS, &old);
struct termios new = old;
new.c_lflag &= ~(ECHO | ICANON);
ioctl (STDIN_FILENO, TCSETS, &new);
int ch = getchar ();
ioctl (STDIN_FILENO, TCSETS, &old);
return ch;
}
int main(void)
{
srand(time(0));//种下伪随机数种子
Queue queue;//声明用于存储蛇身坐标的队列
init(&queue);//初始化队列
Stay snake_head,last_head,apple,last_tail;//声明坐标变量(蛇头 上一轮蛇头 苹果 蛇尾)
snake_head=CreateHead();//初始化舌头位置
apple=CreateApple();//初始化苹果位置
thread_start();//开启键盘操作线程
while(1)
{
if(snake_head.x==apple.x &&snake_head.y==apple.y)//如果蛇头覆盖了苹果坐标
{
apple=CreateApple();//重新创建苹果
enqueue(&queue,CreateTail(last_head));//创建新蛇身并入队
}
else
{
if(!isEmpty(&queue))//如果蛇身队列非空
{
last_tail=dequeue(&queue);//获取当前蛇尾
remove_last(last_tail);//删除当前蛇尾
enqueue(&queue,get_tail(last_head));//蛇头移动后蛇身随之移动到到该位置,将上一轮蛇头坐标作为移动后的蛇身坐标入队
MoveTail(last_head);//将上一轮蛇头位置重新标记为蛇身
}
}
system("clear");//打印界面前清屏
show();//打印界面
usleep(TURN_NEXT);//进程进入暂时睡眠状态
if(strchr("Qq",ch))//如果按了Q键
{
break;//退出循环,结束游戏
}
else
{
last_head=snake_head;//否则将本轮蛇头赋值给上一轮蛇头
if(MoveHead(ch,&snake_head))//根据玩家选择的方向转动蛇头,如果蛇头撞墙或是咬到自己
{
printf("Game over!\nPush \'Q\' to quit\n");//宣告游戏失败
break;//退出循环,结束游戏
}
}
}
thread_wait();//等待玩家按下Q键关闭线程
deinit(&queue);//将队列恢复初始化状态,回收动态分配的内存
return 0;
}
1.二维数组、简单的字符处理、
循环体、结构体、随机数
2.数据结构之队列
3.线程控制
4.输入输出流
编程思想:
1.实时显示界面可用二维数组实现,
要注意区分当前位置是蛇头、蛇身、苹果还是墙
2.贪吃蛇的蛇头和苹果位置要在程序最开始指定,
也就是蛇头和苹果的初始化工作,
蛇头可以使用随机数确定也可以写死,
苹果则必须随机指定;
3.我们输入数据必须经过缓冲区,
如果迟迟未输入必然使游戏出于阻塞状态
因此要解决两个技术问题:
(1)如何从键盘直接读取数据,
读到数据不需回车也不需显示在终端
(2)如何保证在蛇可以自动移动的同时监听键盘事件
当玩家点击方向键才会转向,否则继续前进
第一个问题可以通过临时更改输入输出流解决
第二个问题可以使用同步线程解决
主线程打印游戏数据控制主要流程
子线程控制键盘的监听
4.如何让蛇尾紧随蛇头移动,如何使蛇身可以变长
蛇身是可变长的,应当以什么方式记录蛇身数据,
个人认为选用队列非常贴切,每当蛇吃到苹果时
就会创建一个新蛇身,当蛇移动时蛇尾则要被删除
相较新生成的蛇身而言蛇尾是生成较早的蛇身
典型的先进先出的数据结构
5.要保证游戏能持续运行就必须使用循环体
6.定义坐标结构体,记录蛇头蛇身和苹果的位置
可以方便我们进行编程
首先是队列的构造:
***********************************************************
SnakeQueue.h
------------------------------
#ifndef _SNAKEQUEUE_H_
#define _SNAKEQUEUE_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <sys/types.h>
typedef struct stay_place
{
int x,y;
}Stay;
typedef struct node
{
Stay data;
struct node* next;
}Node;
typedef struct queue
{
Node* head;
Node* tail;
size_t size;
}Queue;
void init(Queue* queue);
void deinit(Queue* queue);
void enqueue(Queue* queue,Stay data);
Stay first(Queue* queue);
Stay last(Queue* queue);
Stay dequeue(Queue* queue);
bool isEmpty(Queue* queue);
int length(Queue* queue);
#endif /*_SNAKEQUEUE_H_*/
************************************************************
SnakeQueue.c
----------------------------------------
#include "SnakeQueue.h"
static Node* create(Stay data)
{
Node* node = (Node*)malloc (sizeof (Node));
node->data = data;
node->next = NULL;
return node;
}
static Node* destroy(Node* node)
{
Node* next = node->next;
free (node);
return next;
}
void init(Queue* queue)
{
queue->head=NULL;
queue->tail=NULL;
queue->size=0;
}
void deinit (Queue* queue)
{
while (queue->head)
{
queue->head=destroy(queue->head);
}
queue->tail=NULL;
queue->size=0;
}
void enqueue(Queue* queue,Stay data)
{
Node* node=create(data);
node->next=NULL;
if(!queue->head)
{
queue->head=node;
}
else
{
queue->tail->next=node;
}
queue->tail=node;
queue->size++;
}
Stay first(Queue* queue)
{
return queue->head->data;
}
Stay last(Queue* queue)
{
return queue->tail->data;
}
Stay dequeue(Queue* queue)
{
Stay data=queue->head->data;
if (!(queue->head=destroy(queue->head)))
{
queue->tail=NULL;
}
queue->size--;
return data;
}
bool isEmpty(Queue* queue)
{
return !queue->size;
}
int length(Queue* queue)
{
return queue->size;
}
***********************************************************************************
Snake.c
------------------------------------------------------------
#include <stdio.h>
#include <unistd.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/time.h>
#include "SnakeQueue.c"
#define SPACE_SIZE 10
#define WALL_SIZE 12
#define WALL -1
#define SPACE 0
#define SNAKE_HEAD 1
#define SNAKE_BODY 2
#define APPLE 3
#define SNAKE_UP 65
#define SNAKE_DOWN 66
#define SNAKE_LEFT 68
#define SNAKE_RIGHT 67
#define TURN_NEXT 500000
#define SHOW_HEAD " @"
#define SHOW_BODY " O"
#define SHOW_APPLE " $"
#define SHOW_WALL " #"
#define SHOW_SPACE " "
#define START_HEAD_X 5
#define START_HEAD_Y 5
int game[WALL_SIZE][WALL_SIZE]={SPACE};
pthread_t snake_thread;
pthread_mutex_t mut;
char ch=SNAKE_RIGHT;
void* snake_move()
{
while(1)
{
pthread_mutex_lock(&mut);
ch=getch();
if(strchr("Qq",ch))
{
break;
}
ch=getch();
ch=getch();
pthread_mutex_unlock(&mut);
}
pthread_exit(NULL);
}
void thread_start()
{
int temp;
memset(&snake_thread,0,sizeof(snake_thread));
temp = pthread_create(&snake_thread,NULL,snake_move,NULL);
}
void thread_wait()
{
if(snake_thread!=0)
{
pthread_join(snake_thread,NULL);
}
}
/*界面显示*/
void show()
{
int i,j;
for(i=0;i<12;++i)
{
for(j=0;j<12;++j)
{
if(i==0 || j==0 || i==11 || j==11)
{
game[i][j]=WALL;
printf(SHOW_WALL);
}
else if(game[i][j]==SNAKE_HEAD)printf(SHOW_HEAD);
else if(game[i][j]==SNAKE_BODY)printf(SHOW_BODY);
else if(game[i][j]==APPLE)printf(SHOW_APPLE);
else printf(SHOW_SPACE);
}
printf("\n");
}
}
/*初始化*/
Stay CreateHead()
{
Stay stay;
/*
while(1)
{
stay.x=rand()%SPACE_SIZE+1;//随机生成蛇头坐标
stay.y=rand()%SPACE_SIZE+1;//随机生成蛇头坐标
if(game[stay.x][stay.y]==SPACE)//如果当前坐标为空则生成成功,否则生成失败重新生成
{
break;
}
}
*/
stay.x=START_HEAD_X;//指定蛇头初始坐标
stay.y=START_HEAD_Y;//指定蛇头初始坐标
game[stay.x][stay.y]=SNAKE_HEAD;
return stay;
}
/*创建*/
Stay CreateApple()
{
Stay stay;
while(1)
{
stay.x=rand()%SPACE_SIZE+1;//随机生成苹果坐标
stay.y=rand()%SPACE_SIZE+1;//随机生成苹果坐标
if(game[stay.x][stay.y]==SPACE)//如果当前坐标为空则生成成功,否则生成失败重新生成
{
break;
}
}
game[stay.x][stay.y]=3;//将成功生成苹果的位置设置为苹果
return stay;
}
Stay CreateTail(Stay stay)
{
game[stay.x][stay.y]=SNAKE_BODY;//将此位置设为蛇身
return stay;//返回蛇身位置(直接将同一变量入队会导致整个队列数据都变化,因此使用函数作为中介将复制数据入队)
}
void remove_last(Stay stay)
{
game[stay.x][stay.y]=SPACE;//将此位置恢复为空
}
int MoveHead(char ch,Stay* stay)
{
game[stay->x][stay->y]=SPACE;//将旧的蛇头位置清零
switch(ch)
{
case SNAKE_UP:stay->x--;//向上移动
break;
case SNAKE_DOWN:stay->x++;//向下移动
break;
case SNAKE_LEFT:stay->y--;//向左移动
break;
case SNAKE_RIGHT:stay->y++;//向右移动
break;
default:break;
}
if(game[stay->x][stay->y]!=SPACE && game[stay->x][stay->y]!=APPLE)//如果蛇头的位置既不为空又不是苹果位置,说明蛇头撞墙或是咬到了自己
{
return -1;//返回失败状态
}
game[stay->x][stay->y]=SNAKE_HEAD;//将新位置设置为蛇头
return 0;
}
void MoveTail(Stay stay)
{
game[stay.x][stay.y]=SNAKE_BODY;//将此位置设为蛇身
}
Stay get_tail(Stay stay)
{
return stay;//返回蛇尾位置(直接将同一变量入队会导致整个队列数据都变化,因此使用函数作为中介将复制数据入队)
}
/*键盘控制*/
int getch (void)
{
struct termios old;
ioctl (STDIN_FILENO, TCGETS, &old);
struct termios new = old;
new.c_lflag &= ~(ECHO | ICANON);
ioctl (STDIN_FILENO, TCSETS, &new);
int ch = getchar ();
ioctl (STDIN_FILENO, TCSETS, &old);
return ch;
}
int main(void)
{
srand(time(0));//种下伪随机数种子
Queue queue;//声明用于存储蛇身坐标的队列
init(&queue);//初始化队列
Stay snake_head,last_head,apple,last_tail;//声明坐标变量(蛇头 上一轮蛇头 苹果 蛇尾)
snake_head=CreateHead();//初始化舌头位置
apple=CreateApple();//初始化苹果位置
thread_start();//开启键盘操作线程
while(1)
{
if(snake_head.x==apple.x &&snake_head.y==apple.y)//如果蛇头覆盖了苹果坐标
{
apple=CreateApple();//重新创建苹果
enqueue(&queue,CreateTail(last_head));//创建新蛇身并入队
}
else
{
if(!isEmpty(&queue))//如果蛇身队列非空
{
last_tail=dequeue(&queue);//获取当前蛇尾
remove_last(last_tail);//删除当前蛇尾
enqueue(&queue,get_tail(last_head));//蛇头移动后蛇身随之移动到到该位置,将上一轮蛇头坐标作为移动后的蛇身坐标入队
MoveTail(last_head);//将上一轮蛇头位置重新标记为蛇身
}
}
system("clear");//打印界面前清屏
show();//打印界面
usleep(TURN_NEXT);//进程进入暂时睡眠状态
if(strchr("Qq",ch))//如果按了Q键
{
break;//退出循环,结束游戏
}
else
{
last_head=snake_head;//否则将本轮蛇头赋值给上一轮蛇头
if(MoveHead(ch,&snake_head))//根据玩家选择的方向转动蛇头,如果蛇头撞墙或是咬到自己
{
printf("Game over!\nPush \'Q\' to quit\n");//宣告游戏失败
break;//退出循环,结束游戏
}
}
}
thread_wait();//等待玩家按下Q键关闭线程
deinit(&queue);//将队列恢复初始化状态,回收动态分配的内存
return 0;
}